mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-12-23 05:23:17 +00:00
commit
40a362c1c7
79 changed files with 2035 additions and 954 deletions
|
@ -12,16 +12,16 @@ TRUST_PROXIES="*"
|
||||||
|
|
||||||
LOG_CHANNEL=stack
|
LOG_CHANNEL=stack
|
||||||
|
|
||||||
DB_CONNECTION=mysql
|
DB_CONNECTION=sqlite
|
||||||
DB_HOST=127.0.0.1
|
DB_HOST=127.0.0.1
|
||||||
DB_PORT=3306
|
DB_PORT=3306
|
||||||
DB_DATABASE=
|
DB_DATABASE='tests/database.sqlite'
|
||||||
DB_USERNAME=
|
DB_USERNAME=
|
||||||
DB_PASSWORD=
|
DB_PASSWORD=
|
||||||
|
|
||||||
BROADCAST_DRIVER=log
|
BROADCAST_DRIVER=log
|
||||||
CACHE_DRIVER=redis
|
CACHE_DRIVER=array
|
||||||
SESSION_DRIVER=redis
|
SESSION_DRIVER=array
|
||||||
SESSION_LIFETIME=120
|
SESSION_LIFETIME=120
|
||||||
QUEUE_DRIVER=redis
|
QUEUE_DRIVER=redis
|
||||||
|
|
||||||
|
|
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -13,3 +13,10 @@ npm-debug.log
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
.env
|
.env
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.bash_profile
|
||||||
|
.bash_history
|
||||||
|
.bashrc
|
||||||
|
.gitconfig
|
||||||
|
.git-credentials
|
||||||
|
/.composer/
|
||||||
|
/nginx.conf
|
||||||
|
|
|
@ -213,19 +213,15 @@ class BaseApiController extends Controller
|
||||||
$profile = $user->profile;
|
$profile = $user->profile;
|
||||||
|
|
||||||
if(config('pixelfed.enforce_account_limit') == true) {
|
if(config('pixelfed.enforce_account_limit') == true) {
|
||||||
$size = Media::whereUserId($user->id)->sum('size') / 1000;
|
$size = Cache::remember($user->storageUsedKey(), now()->addDays(3), function() use($user) {
|
||||||
|
return Media::whereUserId($user->id)->sum('size') / 1000;
|
||||||
|
});
|
||||||
$limit = (int) config('pixelfed.max_account_size');
|
$limit = (int) config('pixelfed.max_account_size');
|
||||||
if ($size >= $limit) {
|
if ($size >= $limit) {
|
||||||
abort(403, 'Account size limit reached.');
|
abort(403, 'Account size limit reached.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$recent = Media::whereProfileId($profile->id)->whereNull('status_id')->count();
|
|
||||||
|
|
||||||
if($recent > 50) {
|
|
||||||
abort(403);
|
|
||||||
}
|
|
||||||
|
|
||||||
$monthHash = hash('sha1', date('Y').date('m'));
|
$monthHash = hash('sha1', date('Y').date('m'));
|
||||||
$userHash = hash('sha1', $user->id . (string) $user->created_at);
|
$userHash = hash('sha1', $user->id . (string) $user->created_at);
|
||||||
|
|
||||||
|
|
|
@ -81,11 +81,13 @@ class ApiController extends BaseApiController
|
||||||
|
|
||||||
public function composeLocationSearch(Request $request)
|
public function composeLocationSearch(Request $request)
|
||||||
{
|
{
|
||||||
|
abort_if(!Auth::check(), 403);
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'q' => 'required|string'
|
'q' => 'required|string'
|
||||||
]);
|
]);
|
||||||
|
$q = filter_var($request->input('q'), FILTER_SANITIZE_STRING);
|
||||||
$places = Place::where('name', 'like', '%' . $request->input('q') . '%')
|
$q = '%' . $q . '%';
|
||||||
|
$places = Place::where('name', 'like', $q)
|
||||||
->take(25)
|
->take(25)
|
||||||
->get()
|
->get()
|
||||||
->map(function($r) {
|
->map(function($r) {
|
||||||
|
|
|
@ -63,17 +63,17 @@ class RegisterController extends Controller
|
||||||
'unique:users',
|
'unique:users',
|
||||||
function ($attribute, $value, $fail) {
|
function ($attribute, $value, $fail) {
|
||||||
if (!ctype_alpha($value[0])) {
|
if (!ctype_alpha($value[0])) {
|
||||||
return $fail($attribute.' is invalid. Username must be alpha-numeric and start with a letter.');
|
return $fail('Username is invalid. Username must be alpha-numeric and start with a letter.');
|
||||||
}
|
}
|
||||||
$val = str_replace(['-', '_'], '', $value);
|
$val = str_replace(['_', '-', '.'], '', $value);
|
||||||
if(!ctype_alnum($val)) {
|
if(!ctype_alnum($val)) {
|
||||||
return $fail($attribute . ' is invalid. Username must be alpha-numeric.');
|
return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
$rules = [
|
$rules = [
|
||||||
'name' => 'required|string|max:'.config('pixelfed.max_name_length'),
|
'name' => 'nullable|string|max:'.config('pixelfed.max_name_length'),
|
||||||
'username' => $usernameRules,
|
'username' => $usernameRules,
|
||||||
'email' => 'required|string|email|max:255|unique:users',
|
'email' => 'required|string|email|max:255|unique:users',
|
||||||
'password' => 'required|string|min:8|confirmed',
|
'password' => 'required|string|min:8|confirmed',
|
||||||
|
|
|
@ -65,27 +65,6 @@ class DiscoverController extends Controller
|
||||||
return view('discover.tags.category', compact('tag', 'posts'));
|
return view('discover.tags.category', compact('tag', 'posts'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function showPersonal(Request $request)
|
|
||||||
{
|
|
||||||
abort_if(!Auth::check(), 403);
|
|
||||||
|
|
||||||
$profile = Auth::user()->profile;
|
|
||||||
|
|
||||||
$tags = Cache::remember('profile-'.$profile->id.':hashtags', now()->addMinutes(15), function() use ($profile){
|
|
||||||
return $profile->hashtags()->groupBy('hashtag_id')->inRandomOrder()->take(8)->get();
|
|
||||||
});
|
|
||||||
$following = Cache::remember('profile:following:'.$profile->id, now()->addMinutes(60), function() use ($profile) {
|
|
||||||
$res = Follower::whereProfileId($profile->id)->pluck('following_id');
|
|
||||||
return $res->push($profile->id)->toArray();
|
|
||||||
});
|
|
||||||
$posts = Cache::remember('profile-'.$profile->id.':hashtag-posts', now()->addMinutes(5), function() use ($profile, $following) {
|
|
||||||
$posts = Status::whereScope('public')->withCount(['likes','comments'])->whereNotIn('profile_id', $following)->whereHas('media')->whereType('photo')->orderByDesc('created_at')->take(39)->get();
|
|
||||||
$posts->post_count = Status::whereScope('public')->whereNotIn('profile_id', $following)->whereHas('media')->whereType('photo')->count();
|
|
||||||
return $posts;
|
|
||||||
});
|
|
||||||
return view('discover.personal', compact('posts', 'tags'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function showLoops(Request $request)
|
public function showLoops(Request $request)
|
||||||
{
|
{
|
||||||
if(config('exp.loops') != true) {
|
if(config('exp.loops') != true) {
|
||||||
|
@ -148,4 +127,10 @@ class DiscoverController extends Controller
|
||||||
}
|
}
|
||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function profilesDirectory(Request $request)
|
||||||
|
{
|
||||||
|
$profiles = Profile::whereNull('domain')->simplePaginate(48);
|
||||||
|
return view('discover.profiles.home', compact('profiles'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -228,6 +228,9 @@ class FederationController extends Controller
|
||||||
$id = Helpers::validateUrl($bodyDecoded['id']);
|
$id = Helpers::validateUrl($bodyDecoded['id']);
|
||||||
$keyDomain = parse_url($keyId, PHP_URL_HOST);
|
$keyDomain = parse_url($keyId, PHP_URL_HOST);
|
||||||
$idDomain = parse_url($id, PHP_URL_HOST);
|
$idDomain = parse_url($id, PHP_URL_HOST);
|
||||||
|
if($keyDomain == config('pixelfed.domain.app') || $idDomain == config('pixelfed.domain.app')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if(isset($bodyDecoded['object'])
|
if(isset($bodyDecoded['object'])
|
||||||
&& is_array($bodyDecoded['object'])
|
&& is_array($bodyDecoded['object'])
|
||||||
&& isset($bodyDecoded['object']['attributedTo'])
|
&& isset($bodyDecoded['object']['attributedTo'])
|
||||||
|
@ -248,7 +251,7 @@ class FederationController extends Controller
|
||||||
}
|
}
|
||||||
$pkey = openssl_pkey_get_public($actor->public_key);
|
$pkey = openssl_pkey_get_public($actor->public_key);
|
||||||
$inboxPath = "/users/{$profile->username}/inbox";
|
$inboxPath = "/users/{$profile->username}/inbox";
|
||||||
list($verified, $headers) = HTTPSignature::verify($pkey, $signatureData, $request->headers->all(), $inboxPath, $body);
|
list($verified, $headers) = HttpSignature::verify($pkey, $signatureData, $request->headers->all(), $inboxPath, $body);
|
||||||
if($verified == 1) {
|
if($verified == 1) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -240,7 +240,8 @@ class InternalApiController extends Controller
|
||||||
'media.*.license' => 'nullable|string|max:80',
|
'media.*.license' => 'nullable|string|max:80',
|
||||||
'cw' => 'nullable|boolean',
|
'cw' => 'nullable|boolean',
|
||||||
'visibility' => 'required|string|in:public,private,unlisted|min:2|max:10',
|
'visibility' => 'required|string|in:public,private,unlisted|min:2|max:10',
|
||||||
'place' => 'nullable'
|
'place' => 'nullable',
|
||||||
|
'comments_disabled' => 'nullable|boolean'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if(config('costar.enabled') == true) {
|
if(config('costar.enabled') == true) {
|
||||||
|
@ -255,7 +256,8 @@ class InternalApiController extends Controller
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$profile = Auth::user()->profile;
|
$user = Auth::user();
|
||||||
|
$profile = $user->profile;
|
||||||
$visibility = $request->input('visibility');
|
$visibility = $request->input('visibility');
|
||||||
$medias = $request->input('media');
|
$medias = $request->input('media');
|
||||||
$attachments = [];
|
$attachments = [];
|
||||||
|
@ -287,9 +289,15 @@ class InternalApiController extends Controller
|
||||||
if($request->filled('place')) {
|
if($request->filled('place')) {
|
||||||
$status->place_id = $request->input('place')['id'];
|
$status->place_id = $request->input('place')['id'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if($request->filled('comments_disabled')) {
|
||||||
|
$status->comments_disabled = $request->input('comments_disabled');
|
||||||
|
}
|
||||||
|
|
||||||
$status->caption = strip_tags($request->caption);
|
$status->caption = strip_tags($request->caption);
|
||||||
$status->scope = 'draft';
|
$status->scope = 'draft';
|
||||||
$status->profile_id = $profile->id;
|
$status->profile_id = $profile->id;
|
||||||
|
|
||||||
$status->save();
|
$status->save();
|
||||||
|
|
||||||
foreach($attachments as $media) {
|
foreach($attachments as $media) {
|
||||||
|
@ -308,6 +316,7 @@ class InternalApiController extends Controller
|
||||||
NewStatusPipeline::dispatch($status);
|
NewStatusPipeline::dispatch($status);
|
||||||
Cache::forget('user:account:id:'.$profile->user_id);
|
Cache::forget('user:account:id:'.$profile->user_id);
|
||||||
Cache::forget('profile:status_count:'.$profile->id);
|
Cache::forget('profile:status_count:'.$profile->id);
|
||||||
|
Cache::forget($user->storageUsedKey());
|
||||||
return $status->url();
|
return $status->url();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,56 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Auth, Storage, URL;
|
||||||
|
use App\Media;
|
||||||
|
use Image as Intervention;
|
||||||
|
use App\Jobs\ImageOptimizePipeline\ImageOptimize;
|
||||||
|
|
||||||
class MediaController extends Controller
|
class MediaController extends Controller
|
||||||
{
|
{
|
||||||
//
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware('auth');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
//return view('settings.drive.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function composeUpdate(Request $request, $id)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'file' => function() {
|
||||||
|
return [
|
||||||
|
'required',
|
||||||
|
'mimes:' . config('pixelfed.media_types'),
|
||||||
|
'max:' . config('pixelfed.max_photo_size'),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user = Auth::user();
|
||||||
|
|
||||||
|
$photo = $request->file('file');
|
||||||
|
|
||||||
|
$media = Media::whereUserId($user->id)
|
||||||
|
->whereProfileId($user->profile_id)
|
||||||
|
->whereNull('status_id')
|
||||||
|
->findOrFail($id);
|
||||||
|
|
||||||
|
$fragments = explode('/', $media->media_path);
|
||||||
|
$name = last($fragments);
|
||||||
|
array_pop($fragments);
|
||||||
|
$dir = implode('/', $fragments);
|
||||||
|
$path = $photo->storeAs($dir, $name);
|
||||||
|
$res = [];
|
||||||
|
$res['url'] = URL::temporarySignedRoute(
|
||||||
|
'temp-media', now()->addHours(1), ['profileId' => $media->profile_id, 'mediaId' => $media->id]
|
||||||
|
);
|
||||||
|
ImageOptimize::dispatch($media);
|
||||||
|
return $res;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,12 +12,33 @@ class PlaceController extends Controller
|
||||||
{
|
{
|
||||||
public function show(Request $request, $id, $slug)
|
public function show(Request $request, $id, $slug)
|
||||||
{
|
{
|
||||||
// TODO: Replace with vue component + apis
|
|
||||||
$place = Place::whereSlug($slug)->findOrFail($id);
|
$place = Place::whereSlug($slug)->findOrFail($id);
|
||||||
$posts = Status::wherePlaceId($place->id)
|
$posts = Status::wherePlaceId($place->id)
|
||||||
|
->whereNull('uri')
|
||||||
->whereScope('public')
|
->whereScope('public')
|
||||||
->orderByDesc('created_at')
|
->orderByDesc('created_at')
|
||||||
->paginate(10);
|
->simplePaginate(10);
|
||||||
|
|
||||||
return view('discover.places.show', compact('place', 'posts'));
|
return view('discover.places.show', compact('place', 'posts'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function directoryHome(Request $request)
|
||||||
|
{
|
||||||
|
$places = Place::select('country')
|
||||||
|
->distinct('country')
|
||||||
|
->simplePaginate(48);
|
||||||
|
|
||||||
|
return view('discover.places.directory.home', compact('places'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function directoryCities(Request $request, $country)
|
||||||
|
{
|
||||||
|
$country = urldecode($country);
|
||||||
|
$places = Place::whereCountry($country)
|
||||||
|
->orderBy('name', 'asc')
|
||||||
|
->distinct('name')
|
||||||
|
->simplePaginate(48);
|
||||||
|
|
||||||
|
return view('discover.places.directory.cities', compact('places'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ use App\Transformer\Api\{
|
||||||
RelationshipTransformer,
|
RelationshipTransformer,
|
||||||
StatusTransformer,
|
StatusTransformer,
|
||||||
};
|
};
|
||||||
|
use App\Services\UserFilterService;
|
||||||
use App\Jobs\StatusPipeline\NewStatusPipeline;
|
use App\Jobs\StatusPipeline\NewStatusPipeline;
|
||||||
use League\Fractal\Serializer\ArraySerializer;
|
use League\Fractal\Serializer\ArraySerializer;
|
||||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
||||||
|
@ -234,23 +235,27 @@ class PublicApiController extends Controller
|
||||||
$max = $request->input('max_id');
|
$max = $request->input('max_id');
|
||||||
$limit = $request->input('limit') ?? 3;
|
$limit = $request->input('limit') ?? 3;
|
||||||
|
|
||||||
$private = Cache::remember('profiles:private', now()->addMinutes(1440), function() {
|
// $private = Cache::remember('profiles:private', now()->addMinutes(1440), function() {
|
||||||
return Profile::whereIsPrivate(true)
|
// return Profile::whereIsPrivate(true)
|
||||||
->orWhere('unlisted', true)
|
// ->orWhere('unlisted', true)
|
||||||
->orWhere('status', '!=', null)
|
// ->orWhere('status', '!=', null)
|
||||||
->pluck('id');
|
// ->pluck('id');
|
||||||
});
|
// });
|
||||||
|
|
||||||
if(Auth::check()) {
|
// if(Auth::check()) {
|
||||||
$pid = Auth::user()->profile->id;
|
// // $pid = Auth::user()->profile->id;
|
||||||
$filters = UserFilter::whereUserId($pid)
|
// // $filters = UserFilter::whereUserId($pid)
|
||||||
->whereFilterableType('App\Profile')
|
// // ->whereFilterableType('App\Profile')
|
||||||
->whereIn('filter_type', ['mute', 'block'])
|
// // ->whereIn('filter_type', ['mute', 'block'])
|
||||||
->pluck('filterable_id')->toArray();
|
// // ->pluck('filterable_id')->toArray();
|
||||||
$filtered = array_merge($private->toArray(), $filters);
|
// // $filtered = array_merge($private->toArray(), $filters);
|
||||||
} else {
|
// $filtered = UserFilterService::filters(Auth::user()->profile_id);
|
||||||
$filtered = $private->toArray();
|
// } else {
|
||||||
}
|
// // $filtered = $private->toArray();
|
||||||
|
// $filtered = [];
|
||||||
|
// }
|
||||||
|
|
||||||
|
$filtered = Auth::check() ? UserFilterService::filters(Auth::user()->profile_id) : [];
|
||||||
|
|
||||||
if($min || $max) {
|
if($min || $max) {
|
||||||
$dir = $min ? '>' : '<';
|
$dir = $min ? '>' : '<';
|
||||||
|
@ -276,14 +281,12 @@ class PublicApiController extends Controller
|
||||||
->with('profile', 'hashtags', 'mentions')
|
->with('profile', 'hashtags', 'mentions')
|
||||||
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album'])
|
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album'])
|
||||||
->whereLocal(true)
|
->whereLocal(true)
|
||||||
->whereNull('uri')
|
|
||||||
->whereNotIn('profile_id', $filtered)
|
->whereNotIn('profile_id', $filtered)
|
||||||
->whereNull('in_reply_to_id')
|
|
||||||
->whereNull('reblog_of_id')
|
|
||||||
->whereVisibility('public')
|
->whereVisibility('public')
|
||||||
->orderBy('created_at', 'desc')
|
->orderBy('created_at', 'desc')
|
||||||
->limit($limit)
|
->limit($limit)
|
||||||
->get();
|
->get();
|
||||||
|
//->toSql();
|
||||||
} else {
|
} else {
|
||||||
$timeline = Status::select(
|
$timeline = Status::select(
|
||||||
'id',
|
'id',
|
||||||
|
@ -305,17 +308,16 @@ class PublicApiController extends Controller
|
||||||
)->whereIn('type', ['photo', 'photo:album', 'video', 'video:album'])
|
)->whereIn('type', ['photo', 'photo:album', 'video', 'video:album'])
|
||||||
->with('profile', 'hashtags', 'mentions')
|
->with('profile', 'hashtags', 'mentions')
|
||||||
->whereLocal(true)
|
->whereLocal(true)
|
||||||
->whereNull('uri')
|
|
||||||
->whereNotIn('profile_id', $filtered)
|
->whereNotIn('profile_id', $filtered)
|
||||||
->whereNull('in_reply_to_id')
|
|
||||||
->whereNull('reblog_of_id')
|
|
||||||
->whereVisibility('public')
|
->whereVisibility('public')
|
||||||
->orderBy('created_at', 'desc')
|
->orderBy('created_at', 'desc')
|
||||||
->simplePaginate($limit);
|
->simplePaginate($limit);
|
||||||
|
//->toSql();
|
||||||
}
|
}
|
||||||
|
|
||||||
$fractal = new Fractal\Resource\Collection($timeline, new StatusTransformer());
|
$fractal = new Fractal\Resource\Collection($timeline, new StatusTransformer());
|
||||||
$res = $this->fractal->createData($fractal)->toArray();
|
$res = $this->fractal->createData($fractal)->toArray();
|
||||||
|
// $res = $timeline;
|
||||||
return response()->json($res);
|
return response()->json($res);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -347,20 +349,22 @@ class PublicApiController extends Controller
|
||||||
return $following->push($pid)->toArray();
|
return $following->push($pid)->toArray();
|
||||||
});
|
});
|
||||||
|
|
||||||
$private = Cache::remember('profiles:private', now()->addMinutes(1440), function() {
|
// $private = Cache::remember('profiles:private', now()->addMinutes(1440), function() {
|
||||||
return Profile::whereIsPrivate(true)
|
// return Profile::whereIsPrivate(true)
|
||||||
->orWhere('unlisted', true)
|
// ->orWhere('unlisted', true)
|
||||||
->orWhere('status', '!=', null)
|
// ->orWhere('status', '!=', null)
|
||||||
->pluck('id');
|
// ->pluck('id');
|
||||||
});
|
// });
|
||||||
|
|
||||||
$private = $private->diff($following)->flatten();
|
// $private = $private->diff($following)->flatten();
|
||||||
|
|
||||||
$filters = UserFilter::whereUserId($pid)
|
// $filters = UserFilter::whereUserId($pid)
|
||||||
->whereFilterableType('App\Profile')
|
// ->whereFilterableType('App\Profile')
|
||||||
->whereIn('filter_type', ['mute', 'block'])
|
// ->whereIn('filter_type', ['mute', 'block'])
|
||||||
->pluck('filterable_id')->toArray();
|
// ->pluck('filterable_id')->toArray();
|
||||||
$filtered = array_merge($private->toArray(), $filters);
|
// $filtered = array_merge($private->toArray(), $filters);
|
||||||
|
|
||||||
|
$filtered = Auth::check() ? UserFilterService::filters(Auth::user()->profile_id) : [];
|
||||||
|
|
||||||
if($min || $max) {
|
if($min || $max) {
|
||||||
$dir = $min ? '>' : '<';
|
$dir = $min ? '>' : '<';
|
||||||
|
@ -387,8 +391,6 @@ class PublicApiController extends Controller
|
||||||
->where('id', $dir, $id)
|
->where('id', $dir, $id)
|
||||||
->whereIn('profile_id', $following)
|
->whereIn('profile_id', $following)
|
||||||
->whereNotIn('profile_id', $filtered)
|
->whereNotIn('profile_id', $filtered)
|
||||||
->whereNull('in_reply_to_id')
|
|
||||||
->whereNull('reblog_of_id')
|
|
||||||
->whereIn('visibility',['public', 'unlisted', 'private'])
|
->whereIn('visibility',['public', 'unlisted', 'private'])
|
||||||
->orderBy('created_at', 'desc')
|
->orderBy('created_at', 'desc')
|
||||||
->limit($limit)
|
->limit($limit)
|
||||||
|
@ -415,8 +417,6 @@ class PublicApiController extends Controller
|
||||||
->with('profile', 'hashtags', 'mentions')
|
->with('profile', 'hashtags', 'mentions')
|
||||||
->whereIn('profile_id', $following)
|
->whereIn('profile_id', $following)
|
||||||
->whereNotIn('profile_id', $filtered)
|
->whereNotIn('profile_id', $filtered)
|
||||||
->whereNull('in_reply_to_id')
|
|
||||||
->whereNull('reblog_of_id')
|
|
||||||
->whereIn('visibility',['public', 'unlisted', 'private'])
|
->whereIn('visibility',['public', 'unlisted', 'private'])
|
||||||
->orderBy('created_at', 'desc')
|
->orderBy('created_at', 'desc')
|
||||||
->simplePaginate($limit);
|
->simplePaginate($limit);
|
||||||
|
|
|
@ -54,7 +54,8 @@ class SearchController extends Controller
|
||||||
'id' => (string) $item->id,
|
'id' => (string) $item->id,
|
||||||
'following' => $item->followedBy(Auth::user()->profile),
|
'following' => $item->followedBy(Auth::user()->profile),
|
||||||
'follow_request' => $item->hasFollowRequestById(Auth::user()->profile_id),
|
'follow_request' => $item->hasFollowRequestById(Auth::user()->profile_id),
|
||||||
'thumb' => $item->avatarUrl()
|
'thumb' => $item->avatarUrl(),
|
||||||
|
'local' => (bool) !$item->domain
|
||||||
]
|
]
|
||||||
]];
|
]];
|
||||||
} else if ($type == 'Note') {
|
} else if ($type == 'Note') {
|
||||||
|
@ -92,7 +93,7 @@ class SearchController extends Controller
|
||||||
}
|
}
|
||||||
return $tokens;
|
return $tokens;
|
||||||
});
|
});
|
||||||
$users = Profile::select('username', 'name', 'id')
|
$users = Profile::select('domain', 'username', 'name', 'id')
|
||||||
->whereNull('status')
|
->whereNull('status')
|
||||||
->whereNull('domain')
|
->whereNull('domain')
|
||||||
->where('id', '!=', Auth::user()->profile->id)
|
->where('id', '!=', Auth::user()->profile->id)
|
||||||
|
@ -113,9 +114,11 @@ class SearchController extends Controller
|
||||||
'avatar' => $item->avatarUrl(),
|
'avatar' => $item->avatarUrl(),
|
||||||
'id' => $item->id,
|
'id' => $item->id,
|
||||||
'entity' => [
|
'entity' => [
|
||||||
'id' => $item->id,
|
'id' => (string) $item->id,
|
||||||
'following' => $item->followedBy(Auth::user()->profile),
|
'following' => $item->followedBy(Auth::user()->profile),
|
||||||
'thumb' => $item->avatarUrl()
|
'follow_request' => $item->hasFollowRequestById(Auth::user()->profile_id),
|
||||||
|
'thumb' => $item->avatarUrl(),
|
||||||
|
'local' => (bool) !$item->domain
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
@ -162,4 +165,5 @@ class SearchController extends Controller
|
||||||
|
|
||||||
return view('search.results');
|
return view('search.results');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@ class StatusController extends Controller
|
||||||
{
|
{
|
||||||
public function show(Request $request, $username, int $id)
|
public function show(Request $request, $username, int $id)
|
||||||
{
|
{
|
||||||
// $id = strlen($id) < 17 ? array_first(\Hashids::decode($id)) : $id;
|
|
||||||
$user = Profile::whereNull('domain')->whereUsername($username)->firstOrFail();
|
$user = Profile::whereNull('domain')->whereUsername($username)->firstOrFail();
|
||||||
|
|
||||||
if($user->status != null) {
|
if($user->status != null) {
|
||||||
|
@ -60,9 +59,24 @@ class StatusController extends Controller
|
||||||
return view($template, compact('user', 'status'));
|
return view($template, compact('user', 'status'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function showId(int $id)
|
||||||
|
{
|
||||||
|
abort(404);
|
||||||
|
$status = Status::whereNull('reblog_of_id')
|
||||||
|
->whereIn('scope', ['public', 'unlisted'])
|
||||||
|
->findOrFail($id);
|
||||||
|
return redirect($status->url());
|
||||||
|
}
|
||||||
|
|
||||||
public function showEmbed(Request $request, $username, int $id)
|
public function showEmbed(Request $request, $username, int $id)
|
||||||
{
|
{
|
||||||
return;
|
abort(404);
|
||||||
|
$profile = Profile::whereNull('status')->whereUsername($username)->first();
|
||||||
|
$status = Status::whereScope('private')->find($id);
|
||||||
|
if(!$profile || !$status) {
|
||||||
|
return view('status.embed-removed');
|
||||||
|
}
|
||||||
|
return view('status.embed', compact('status'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function showObject(Request $request, $username, int $id)
|
public function showObject(Request $request, $username, int $id)
|
||||||
|
@ -77,13 +91,7 @@ class StatusController extends Controller
|
||||||
->whereNotIn('visibility',['draft','direct'])
|
->whereNotIn('visibility',['draft','direct'])
|
||||||
->findOrFail($id);
|
->findOrFail($id);
|
||||||
|
|
||||||
if($status->uri) {
|
abort_if($status->uri, 404);
|
||||||
$url = $status->uri;
|
|
||||||
if(ends_with($url, '/activity')) {
|
|
||||||
$url = str_replace('/activity', '', $url);
|
|
||||||
}
|
|
||||||
return redirect($url);
|
|
||||||
}
|
|
||||||
|
|
||||||
if($status->visibility == 'private' || $user->is_private) {
|
if($status->visibility == 'private' || $user->is_private) {
|
||||||
if(!Auth::check()) {
|
if(!Auth::check()) {
|
||||||
|
@ -190,7 +198,7 @@ class StatusController extends Controller
|
||||||
$resource = new Fractal\Resource\Item($status, new Note());
|
$resource = new Fractal\Resource\Item($status, new Note());
|
||||||
$res = $fractal->createData($resource)->toArray();
|
$res = $fractal->createData($resource)->toArray();
|
||||||
|
|
||||||
return response(json_encode($res['data']))->header('Content-Type', 'application/activity+json');
|
return response()->json($res['data'], 200, ['Content-Type' => 'application/activity+json'], JSON_PRETTY_PRINT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function edit(Request $request, $username, $id)
|
public function edit(Request $request, $username, $id)
|
||||||
|
|
|
@ -19,7 +19,13 @@ class EmailVerificationCheck
|
||||||
if ($request->user() &&
|
if ($request->user() &&
|
||||||
config('pixelfed.enforce_email_verification') &&
|
config('pixelfed.enforce_email_verification') &&
|
||||||
is_null($request->user()->email_verified_at) &&
|
is_null($request->user()->email_verified_at) &&
|
||||||
!$request->is('i/verify-email', 'log*', 'i/confirm-email/*', 'settings/home')
|
!$request->is(
|
||||||
|
'i/verify-email',
|
||||||
|
'log*',
|
||||||
|
'i/confirm-email/*',
|
||||||
|
'settings/home',
|
||||||
|
'settings/email'
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
return redirect('/i/verify-email');
|
return redirect('/i/verify-email');
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,10 @@ class ImageUpdate implements ShouldQueue
|
||||||
$path = storage_path('app/'.$media->media_path);
|
$path = storage_path('app/'.$media->media_path);
|
||||||
$thumb = storage_path('app/'.$media->thumbnail_path);
|
$thumb = storage_path('app/'.$media->thumbnail_path);
|
||||||
|
|
||||||
|
if (!is_file($path)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (in_array($media->mime, $this->protectedMimes) == true) {
|
if (in_array($media->mime, $this->protectedMimes) == true) {
|
||||||
ImageOptimizer::optimize($thumb);
|
ImageOptimizer::optimize($thumb);
|
||||||
ImageOptimizer::optimize($path);
|
ImageOptimizer::optimize($path);
|
||||||
|
|
|
@ -18,6 +18,9 @@ class InboxWorker implements ShouldQueue
|
||||||
protected $profile;
|
protected $profile;
|
||||||
protected $payload;
|
protected $payload;
|
||||||
|
|
||||||
|
public $timeout = 5;
|
||||||
|
public $tries = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new job instance.
|
* Create a new job instance.
|
||||||
*
|
*
|
||||||
|
|
99
app/Observers/UserFilterObserver.php
Normal file
99
app/Observers/UserFilterObserver.php
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Observers;
|
||||||
|
|
||||||
|
use App\UserFilter;
|
||||||
|
use App\Services\UserFilterService;
|
||||||
|
|
||||||
|
class UserFilterObserver
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle the user filter "created" event.
|
||||||
|
*
|
||||||
|
* @param \App\UserFilter $userFilter
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function created(UserFilter $userFilter)
|
||||||
|
{
|
||||||
|
$this->filterCreate($userFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the user filter "updated" event.
|
||||||
|
*
|
||||||
|
* @param \App\UserFilter $userFilter
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function updated(UserFilter $userFilter)
|
||||||
|
{
|
||||||
|
$this->filterCreate($userFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the user filter "deleted" event.
|
||||||
|
*
|
||||||
|
* @param \App\UserFilter $userFilter
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function deleted(UserFilter $userFilter)
|
||||||
|
{
|
||||||
|
$this->filterDelete($userFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the user filter "restored" event.
|
||||||
|
*
|
||||||
|
* @param \App\UserFilter $userFilter
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function restored(UserFilter $userFilter)
|
||||||
|
{
|
||||||
|
$this->filterCreate($userFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the user filter "force deleted" event.
|
||||||
|
*
|
||||||
|
* @param \App\UserFilter $userFilter
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function forceDeleted(UserFilter $userFilter)
|
||||||
|
{
|
||||||
|
$this->filterDelete($userFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function filterCreate(UserFilter $userFilter)
|
||||||
|
{
|
||||||
|
if($userFilter->filterable_type !== 'App\Profile') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($userFilter->filter_type) {
|
||||||
|
case 'mute':
|
||||||
|
UserFilterService::mute($userFilter->user_id, $userFilter->filterable_id);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'block':
|
||||||
|
UserFilterService::block($userFilter->user_id, $userFilter->filterable_id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function filterDelete(UserFilter $userFilter)
|
||||||
|
{
|
||||||
|
if($userFilter->filterable_type !== 'App\Profile') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($userFilter->filter_type) {
|
||||||
|
case 'mute':
|
||||||
|
UserFilterService::unmute($userFilter->user_id, $userFilter->filterable_id);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'block':
|
||||||
|
UserFilterService::unblock($userFilter->user_id, $userFilter->filterable_id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,4 +28,16 @@ class Place extends Model
|
||||||
{
|
{
|
||||||
return $this->hasMany(Status::class, 'id', 'place_id');
|
return $this->hasMany(Status::class, 'id', 'place_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function countryUrl()
|
||||||
|
{
|
||||||
|
$country = strtolower($this->country);
|
||||||
|
$country = urlencode($country);
|
||||||
|
return url('/discover/location/country/' . $country);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cityUrl()
|
||||||
|
{
|
||||||
|
return $this->url();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,15 @@ use App\Observers\{
|
||||||
AvatarObserver,
|
AvatarObserver,
|
||||||
NotificationObserver,
|
NotificationObserver,
|
||||||
StatusHashtagObserver,
|
StatusHashtagObserver,
|
||||||
UserObserver
|
UserObserver,
|
||||||
|
UserFilterObserver,
|
||||||
};
|
};
|
||||||
use App\{
|
use App\{
|
||||||
Avatar,
|
Avatar,
|
||||||
Notification,
|
Notification,
|
||||||
StatusHashtag,
|
StatusHashtag,
|
||||||
User
|
User,
|
||||||
|
UserFilter
|
||||||
};
|
};
|
||||||
use Auth, Horizon, URL;
|
use Auth, Horizon, URL;
|
||||||
use Illuminate\Support\Facades\Blade;
|
use Illuminate\Support\Facades\Blade;
|
||||||
|
@ -35,6 +37,7 @@ class AppServiceProvider extends ServiceProvider
|
||||||
Notification::observe(NotificationObserver::class);
|
Notification::observe(NotificationObserver::class);
|
||||||
StatusHashtag::observe(StatusHashtagObserver::class);
|
StatusHashtag::observe(StatusHashtagObserver::class);
|
||||||
User::observe(UserObserver::class);
|
User::observe(UserObserver::class);
|
||||||
|
UserFilter::observe(UserFilterObserver::class);
|
||||||
|
|
||||||
Horizon::auth(function ($request) {
|
Horizon::auth(function ($request) {
|
||||||
return Auth::check() && $request->user()->is_admin;
|
return Auth::check() && $request->user()->is_admin;
|
||||||
|
|
100
app/Services/UserFilterService.php
Normal file
100
app/Services/UserFilterService.php
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use Cache, Redis;
|
||||||
|
|
||||||
|
use App\{
|
||||||
|
Follower,
|
||||||
|
Profile,
|
||||||
|
UserFilter
|
||||||
|
};
|
||||||
|
|
||||||
|
class UserFilterService {
|
||||||
|
|
||||||
|
const USER_MUTES_KEY = 'pf:services:mutes:ids:';
|
||||||
|
const USER_BLOCKS_KEY = 'pf:services:blocks:ids:';
|
||||||
|
|
||||||
|
public static function mutes(int $profile_id) : array
|
||||||
|
{
|
||||||
|
$key = self::USER_MUTES_KEY . $profile_id;
|
||||||
|
$cached = Redis::zrevrange($key, 0, -1);
|
||||||
|
if($cached) {
|
||||||
|
return $cached;
|
||||||
|
} else {
|
||||||
|
$ids = UserFilter::whereFilterType('mute')
|
||||||
|
->whereUserId($profile_id)
|
||||||
|
->pluck('filterable_id')
|
||||||
|
->toArray();
|
||||||
|
foreach ($ids as $muted_id) {
|
||||||
|
Redis::zadd($key, (int) $muted_id, (int) $muted_id);
|
||||||
|
}
|
||||||
|
return $ids;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function blocks(int $profile_id) : array
|
||||||
|
{
|
||||||
|
$key = self::USER_BLOCKS_KEY . $profile_id;
|
||||||
|
$cached = Redis::zrevrange($key, 0, -1);
|
||||||
|
if($cached) {
|
||||||
|
return $cached;
|
||||||
|
} else {
|
||||||
|
$ids = UserFilter::whereFilterType('block')
|
||||||
|
->whereUserId($profile_id)
|
||||||
|
->pluck('filterable_id')
|
||||||
|
->toArray();
|
||||||
|
foreach ($ids as $blocked_id) {
|
||||||
|
Redis::zadd($key, $blocked_id, $blocked_id);
|
||||||
|
}
|
||||||
|
return $ids;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function filters(int $profile_id) : array
|
||||||
|
{
|
||||||
|
return array_merge(self::mutes($profile_id), self::blocks($profile_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function mute(int $profile_id, int $muted_id)
|
||||||
|
{
|
||||||
|
$key = self::USER_MUTES_KEY . $profile_id;
|
||||||
|
$mutes = self::mutes($profile_id);
|
||||||
|
$exists = in_array($muted_id, $mutes);
|
||||||
|
if(!$exists) {
|
||||||
|
Redis::zadd($key, $muted_id, $muted_id);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function unmute(int $profile_id, string $muted_id)
|
||||||
|
{
|
||||||
|
$key = self::USER_MUTES_KEY . $profile_id;
|
||||||
|
$mutes = self::mutes($profile_id);
|
||||||
|
$exists = in_array($muted_id, $mutes);
|
||||||
|
if($exists) {
|
||||||
|
Redis::zrem($key, $muted_id);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function block(int $profile_id, int $blocked_id)
|
||||||
|
{
|
||||||
|
$key = self::USER_BLOCKS_KEY . $profile_id;
|
||||||
|
$exists = in_array($blocked_id, self::blocks($profile_id));
|
||||||
|
if(!$exists) {
|
||||||
|
Redis::zadd($key, $blocked_id, $blocked_id);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function unblock(int $profile_id, string $blocked_id)
|
||||||
|
{
|
||||||
|
$key = self::USER_BLOCKS_KEY . $profile_id;
|
||||||
|
$exists = in_array($blocked_id, self::blocks($profile_id));
|
||||||
|
if($exists) {
|
||||||
|
Redis::zrem($key, $blocked_id);
|
||||||
|
}
|
||||||
|
return $exists;
|
||||||
|
}
|
||||||
|
}
|
|
@ -54,6 +54,13 @@ class StatusTransformer extends Fractal\TransformerAbstract
|
||||||
];
|
];
|
||||||
}),
|
}),
|
||||||
'tag' => [],
|
'tag' => [],
|
||||||
|
'location' => $status->place_id ? [
|
||||||
|
'type' => 'Place',
|
||||||
|
'name' => $status->place->name,
|
||||||
|
'longitude' => $status->place->long,
|
||||||
|
'latitude' => $status->place->lat,
|
||||||
|
'country' => $status->place->country
|
||||||
|
] : null,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,14 @@ class CreateNote extends Fractal\TransformerAbstract
|
||||||
'announce' => 'https://www.w3.org/ns/activitystreams#Public',
|
'announce' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||||
'like' => 'https://www.w3.org/ns/activitystreams#Public',
|
'like' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||||
'reply' => $status->comments_disabled == true ? null : 'https://www.w3.org/ns/activitystreams#Public'
|
'reply' => $status->comments_disabled == true ? null : 'https://www.w3.org/ns/activitystreams#Public'
|
||||||
]
|
],
|
||||||
|
'location' => $status->place_id ? [
|
||||||
|
'type' => 'Place',
|
||||||
|
'name' => $status->place->name,
|
||||||
|
'longitude' => $status->place->long,
|
||||||
|
'latitude' => $status->place->lat,
|
||||||
|
'country' => $status->place->country
|
||||||
|
] : null,
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,14 @@ class Note extends Fractal\TransformerAbstract
|
||||||
'announce' => 'https://www.w3.org/ns/activitystreams#Public',
|
'announce' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||||
'like' => 'https://www.w3.org/ns/activitystreams#Public',
|
'like' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||||
'reply' => $status->comments_disabled == true ? null : 'https://www.w3.org/ns/activitystreams#Public'
|
'reply' => $status->comments_disabled == true ? null : 'https://www.w3.org/ns/activitystreams#Public'
|
||||||
]
|
],
|
||||||
|
'location' => $status->place_id ? [
|
||||||
|
'type' => 'Place',
|
||||||
|
'name' => $status->place->name,
|
||||||
|
'longitude' => $status->place->long,
|
||||||
|
'latitude' => $status->place->lat,
|
||||||
|
'country' => $status->place->country
|
||||||
|
] : null,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,16 @@
|
||||||
|
|
||||||
namespace App\Transformer\Api;
|
namespace App\Transformer\Api;
|
||||||
|
|
||||||
|
use Auth;
|
||||||
use App\Profile;
|
use App\Profile;
|
||||||
use League\Fractal;
|
use League\Fractal;
|
||||||
|
|
||||||
class AccountTransformer extends Fractal\TransformerAbstract
|
class AccountTransformer extends Fractal\TransformerAbstract
|
||||||
{
|
{
|
||||||
|
protected $defaultIncludes = [
|
||||||
|
'relationship',
|
||||||
|
];
|
||||||
|
|
||||||
public function transform(Profile $profile)
|
public function transform(Profile $profile)
|
||||||
{
|
{
|
||||||
$is_admin = $profile->domain ? false : $profile->user->is_admin;
|
$is_admin = $profile->domain ? false : $profile->user->is_admin;
|
||||||
|
@ -32,7 +37,12 @@ class AccountTransformer extends Fractal\TransformerAbstract
|
||||||
'bot' => null,
|
'bot' => null,
|
||||||
'website' => $profile->website,
|
'website' => $profile->website,
|
||||||
'software' => 'pixelfed',
|
'software' => 'pixelfed',
|
||||||
'is_admin' => (bool) $is_admin
|
'is_admin' => (bool) $is_admin,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function includeRelationship(Profile $profile)
|
||||||
|
{
|
||||||
|
return $this->item($profile, new RelationshipTransformer());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,9 @@ class RelationshipTransformer extends Fractal\TransformerAbstract
|
||||||
public function transform(Profile $profile)
|
public function transform(Profile $profile)
|
||||||
{
|
{
|
||||||
$auth = Auth::check();
|
$auth = Auth::check();
|
||||||
|
if(!$auth) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
$user = $auth ? Auth::user()->profile : false;
|
$user = $auth ? Auth::user()->profile : false;
|
||||||
$requested = false;
|
$requested = false;
|
||||||
if($user) {
|
if($user) {
|
||||||
|
|
|
@ -78,4 +78,9 @@ class User extends Authenticatable
|
||||||
return $this->hasMany(UserDevice::class);
|
return $this->hasMany(UserDevice::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function storageUsedKey()
|
||||||
|
{
|
||||||
|
return 'profile:storage:used:' . $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,8 @@ use App\{
|
||||||
Like,
|
Like,
|
||||||
Notification,
|
Notification,
|
||||||
Profile,
|
Profile,
|
||||||
Status
|
Status,
|
||||||
|
StatusHashtag,
|
||||||
};
|
};
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use App\Util\ActivityPub\Helpers;
|
use App\Util\ActivityPub\Helpers;
|
||||||
|
@ -129,7 +130,7 @@ class Inbox
|
||||||
}
|
}
|
||||||
|
|
||||||
$inReplyTo = $activity['inReplyTo'];
|
$inReplyTo = $activity['inReplyTo'];
|
||||||
$url = $activity['id'];
|
$url = isset($activity['url']) ? $activity['url'] : $activity['id'];
|
||||||
|
|
||||||
Helpers::statusFirstOrFetch($url, true);
|
Helpers::statusFirstOrFetch($url, true);
|
||||||
return;
|
return;
|
||||||
|
@ -147,7 +148,7 @@ class Inbox
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$url = $activity['id'];
|
$url = isset($activity['url']) ? $activity['url'] : $activity['id'];
|
||||||
if(Status::whereUrl($url)->exists()) {
|
if(Status::whereUrl($url)->exists()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -285,28 +286,74 @@ class Inbox
|
||||||
|
|
||||||
public function handleDeleteActivity()
|
public function handleDeleteActivity()
|
||||||
{
|
{
|
||||||
|
if(!isset(
|
||||||
|
$this->payload['actor'],
|
||||||
|
$this->payload['object'],
|
||||||
|
$this->payload['object']['id'],
|
||||||
|
$this->payload['object']['type']
|
||||||
|
)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
$actor = $this->payload['actor'];
|
$actor = $this->payload['actor'];
|
||||||
$obj = $this->payload['object'];
|
$obj = $this->payload['object'];
|
||||||
abort_if(!Helpers::validateUrl($obj), 400);
|
$type = $this->payload['object']['type'];
|
||||||
if(is_string($obj) && Helpers::validateUrl($obj)) {
|
$typeCheck = in_array($type, ['Person', 'Tombstone']);
|
||||||
// actor object detected
|
if(!Helpers::validateUrl($actor) || !Helpers::validateUrl($obj['id']) || !$typeCheck) {
|
||||||
// todo delete actor
|
|
||||||
return;
|
return;
|
||||||
} else if (Helpers::validateUrl($obj['id']) && Helpers::validateObject($obj) && $obj['type'] == 'Tombstone') {
|
}
|
||||||
// todo delete status or object
|
if(parse_url($obj['id'], PHP_URL_HOST) !== parse_url($actor, PHP_URL_HOST)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
$id = $this->payload['object']['id'];
|
||||||
|
switch ($type) {
|
||||||
|
case 'Person':
|
||||||
|
$profile = Profile::whereNull('domain')
|
||||||
|
->whereNull('private_key')
|
||||||
|
->whereRemoteUrl($id)
|
||||||
|
->first();
|
||||||
|
if(!$profile) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Notification::whereActorId($profile->id)->delete();
|
||||||
|
$profile->avatar()->delete();
|
||||||
|
$profile->followers()->delete();
|
||||||
|
$profile->following()->delete();
|
||||||
|
$profile->likes()->delete();
|
||||||
|
$profile->media()->delete();
|
||||||
|
$profile->statuses()->delete();
|
||||||
|
$profile->delete();
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Tombstone':
|
||||||
|
$status = Status::whereUri($id)->first();
|
||||||
|
if(!$status) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$status->media->delete();
|
||||||
|
$status->delete();
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handleLikeActivity()
|
public function handleLikeActivity()
|
||||||
{
|
{
|
||||||
$actor = $this->payload['actor'];
|
$actor = $this->payload['actor'];
|
||||||
|
|
||||||
abort_if(!Helpers::validateUrl($actor), 400);
|
if(!Helpers::validateUrl($actor)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$profile = self::actorFirstOrCreate($actor);
|
$profile = self::actorFirstOrCreate($actor);
|
||||||
$obj = $this->payload['object'];
|
$obj = $this->payload['object'];
|
||||||
abort_if(!Helpers::validateLocalUrl($obj), 400);
|
if(!Helpers::validateUrl($obj)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
$status = Helpers::statusFirstOrFetch($obj);
|
$status = Helpers::statusFirstOrFetch($obj);
|
||||||
if(!$status || !$profile) {
|
if(!$status || !$profile) {
|
||||||
return;
|
return;
|
||||||
|
@ -343,7 +390,9 @@ class Inbox
|
||||||
|
|
||||||
case 'Announce':
|
case 'Announce':
|
||||||
$obj = $obj['object'];
|
$obj = $obj['object'];
|
||||||
abort_if(!Helpers::validateLocalUrl($obj), 400);
|
if(!Helpers::validateLocalUrl($obj)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
$status = Helpers::statusFetch($obj);
|
$status = Helpers::statusFetch($obj);
|
||||||
if(!$status) {
|
if(!$status) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -83,4 +83,19 @@ trait User {
|
||||||
{
|
{
|
||||||
return 100;
|
return 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getMaxComposeMediaUpdatesPerHourAttribute()
|
||||||
|
{
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMaxComposeMediaUpdatesPerDayAttribute()
|
||||||
|
{
|
||||||
|
return 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMaxComposeMediaUpdatesPerMonthAttribute()
|
||||||
|
{
|
||||||
|
return 5000;
|
||||||
|
}
|
||||||
}
|
}
|
617
composer.lock
generated
617
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -15,6 +15,6 @@ return [
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'driver' => 'gd',
|
'driver' => env('IMAGE_DRIVER', 'gd'),
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
|
@ -25,7 +25,7 @@ return [
|
||||||
|
|
||||||
'timeline' => [
|
'timeline' => [
|
||||||
'local' => [
|
'local' => [
|
||||||
'is_public' => env('INSTANCE_PUBLIC_LOCAL_TIMELINE', true)
|
'is_public' => env('INSTANCE_PUBLIC_LOCAL_TIMELINE', false)
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ return [
|
||||||
| This value is the version of your Pixelfed instance.
|
| This value is the version of your Pixelfed instance.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
'version' => '0.10.0',
|
'version' => '0.10.2',
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
@ -16,7 +16,7 @@ return [
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'driver' => env('SESSION_DRIVER', 'file'),
|
'driver' => env('SESSION_DRIVER', 'database'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
@ -29,7 +29,7 @@ return [
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'lifetime' => env('SESSION_LIFETIME', 120),
|
'lifetime' => env('SESSION_LIFETIME', 2880),
|
||||||
|
|
||||||
'expire_on_close' => false,
|
'expire_on_close' => false,
|
||||||
|
|
||||||
|
@ -109,7 +109,7 @@ return [
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'lottery' => [2, 100],
|
'lottery' => [2, 1000],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
@ -122,10 +122,7 @@ return [
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'cookie' => env(
|
'cookie' => 'pxfs',
|
||||||
'SESSION_COOKIE',
|
|
||||||
str_slug(env('APP_NAME', 'laravel'), '_').'_session'
|
|
||||||
),
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
@ -151,7 +148,7 @@ return [
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'domain' => env('SESSION_DOMAIN', null),
|
'domain' => env('SESSION_DOMAIN', env('APP_DOMAIN', null)),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
@ -164,7 +161,7 @@ return [
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'secure' => env('SESSION_SECURE_COOKIE', false),
|
'secure' => true,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
@ -47,7 +47,7 @@ return [
|
||||||
/*
|
/*
|
||||||
* This path will be used to register the necessary routes for the package.
|
* This path will be used to register the necessary routes for the package.
|
||||||
*/
|
*/
|
||||||
'path' => 'laravel-websockets',
|
'path' => 'pxws',
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Dashboard Routes Middleware
|
* Dashboard Routes Middleware
|
||||||
|
@ -98,18 +98,20 @@ return [
|
||||||
* certificate chain of issuers. The private key also may be contained
|
* certificate chain of issuers. The private key also may be contained
|
||||||
* in a separate file specified by local_pk.
|
* in a separate file specified by local_pk.
|
||||||
*/
|
*/
|
||||||
'local_cert' => null,
|
'local_cert' => env('WSS_LOCAL_CERT', null),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Path to local private key file on filesystem in case of separate files for
|
* Path to local private key file on filesystem in case of separate files for
|
||||||
* certificate (local_cert) and private key.
|
* certificate (local_cert) and private key.
|
||||||
*/
|
*/
|
||||||
'local_pk' => null,
|
'local_pk' => env('WSS_LOCAL_PK', null),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Passphrase for your local_cert file.
|
* Passphrase for your local_cert file.
|
||||||
*/
|
*/
|
||||||
'passphrase' => null,
|
'passphrase' => env('WSS_PASSPHRASE', null),
|
||||||
|
|
||||||
|
'verify_peer' => env('WSS_VERIFY_PEER', false),
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
83
package-lock.json
generated
83
package-lock.json
generated
|
@ -797,6 +797,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz",
|
||||||
"integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw=="
|
"integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw=="
|
||||||
},
|
},
|
||||||
|
"@trevoreyre/autocomplete-vue": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@trevoreyre/autocomplete-vue/-/autocomplete-vue-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-2A/SQjtZOX1/4AAsw/EkblMKw1xH+EJj99K2tRXu8umUpBFWUFk3PXCa7NlBE3fv2YSKf7fGEn1i225xtiulUg=="
|
||||||
|
},
|
||||||
"@types/events": {
|
"@types/events": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
|
||||||
|
@ -2241,9 +2246,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"combined-stream": {
|
"combined-stream": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
"integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==",
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"delayed-stream": "~1.0.0"
|
"delayed-stream": "~1.0.0"
|
||||||
}
|
}
|
||||||
|
@ -2516,6 +2521,11 @@
|
||||||
"sha.js": "^2.4.8"
|
"sha.js": "^2.4.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"cropperjs": {
|
||||||
|
"version": "1.5.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/cropperjs/-/cropperjs-1.5.4.tgz",
|
||||||
|
"integrity": "sha512-2DczDxhqruvDz+oOUjRTXvsIyUnlSDvWG7r7qaUQioPoZAgfA07kaTtdNXAD/8ezA4CHRk1MT7PFMFQVpcD/1Q=="
|
||||||
|
},
|
||||||
"cross-env": {
|
"cross-env": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-5.2.0.tgz",
|
||||||
|
@ -4020,7 +4030,8 @@
|
||||||
},
|
},
|
||||||
"ansi-regex": {
|
"ansi-regex": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"bundled": true
|
"bundled": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"aproba": {
|
"aproba": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
|
@ -4038,11 +4049,13 @@
|
||||||
},
|
},
|
||||||
"balanced-match": {
|
"balanced-match": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"bundled": true
|
"bundled": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"brace-expansion": {
|
"brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
"concat-map": "0.0.1"
|
"concat-map": "0.0.1"
|
||||||
|
@ -4055,15 +4068,18 @@
|
||||||
},
|
},
|
||||||
"code-point-at": {
|
"code-point-at": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"bundled": true
|
"bundled": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"concat-map": {
|
"concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"bundled": true
|
"bundled": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"console-control-strings": {
|
"console-control-strings": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"bundled": true
|
"bundled": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"core-util-is": {
|
"core-util-is": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
|
@ -4166,7 +4182,8 @@
|
||||||
},
|
},
|
||||||
"inherits": {
|
"inherits": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"bundled": true
|
"bundled": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"ini": {
|
"ini": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.5",
|
||||||
|
@ -4176,6 +4193,7 @@
|
||||||
"is-fullwidth-code-point": {
|
"is-fullwidth-code-point": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"number-is-nan": "^1.0.0"
|
"number-is-nan": "^1.0.0"
|
||||||
}
|
}
|
||||||
|
@ -4188,17 +4206,20 @@
|
||||||
"minimatch": {
|
"minimatch": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "0.0.8",
|
"version": "0.0.8",
|
||||||
"bundled": true
|
"bundled": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"minipass": {
|
"minipass": {
|
||||||
"version": "2.3.5",
|
"version": "2.3.5",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"safe-buffer": "^5.1.2",
|
"safe-buffer": "^5.1.2",
|
||||||
"yallist": "^3.0.0"
|
"yallist": "^3.0.0"
|
||||||
|
@ -4215,6 +4236,7 @@
|
||||||
"mkdirp": {
|
"mkdirp": {
|
||||||
"version": "0.5.1",
|
"version": "0.5.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"minimist": "0.0.8"
|
"minimist": "0.0.8"
|
||||||
}
|
}
|
||||||
|
@ -4287,7 +4309,8 @@
|
||||||
},
|
},
|
||||||
"number-is-nan": {
|
"number-is-nan": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"bundled": true
|
"bundled": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"object-assign": {
|
"object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
|
@ -4297,6 +4320,7 @@
|
||||||
"once": {
|
"once": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
}
|
}
|
||||||
|
@ -4372,7 +4396,8 @@
|
||||||
},
|
},
|
||||||
"safe-buffer": {
|
"safe-buffer": {
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"bundled": true
|
"bundled": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"safer-buffer": {
|
"safer-buffer": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
|
@ -4402,6 +4427,7 @@
|
||||||
"string-width": {
|
"string-width": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"code-point-at": "^1.0.0",
|
"code-point-at": "^1.0.0",
|
||||||
"is-fullwidth-code-point": "^1.0.0",
|
"is-fullwidth-code-point": "^1.0.0",
|
||||||
|
@ -4419,6 +4445,7 @@
|
||||||
"strip-ansi": {
|
"strip-ansi": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-regex": "^2.0.0"
|
"ansi-regex": "^2.0.0"
|
||||||
}
|
}
|
||||||
|
@ -4457,11 +4484,13 @@
|
||||||
},
|
},
|
||||||
"wrappy": {
|
"wrappy": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"bundled": true
|
"bundled": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"yallist": {
|
"yallist": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"bundled": true
|
"bundled": true,
|
||||||
|
"optional": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -5715,9 +5744,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.11",
|
"version": "4.17.15",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||||
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
|
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
|
||||||
},
|
},
|
||||||
"lodash._baseassign": {
|
"lodash._baseassign": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
|
@ -7654,9 +7683,9 @@
|
||||||
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
|
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
|
||||||
},
|
},
|
||||||
"psl": {
|
"psl": {
|
||||||
"version": "1.1.31",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz",
|
"resolved": "https://registry.npmjs.org/psl/-/psl-1.3.0.tgz",
|
||||||
"integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw=="
|
"integrity": "sha512-avHdspHO+9rQTLbv1RO+MPYeP/SzsCoxofjVnHanETfQhTJrmB0HlDoW+EiN/R+C0BZ+gERab9NY0lPN2TxNag=="
|
||||||
},
|
},
|
||||||
"public-encrypt": {
|
"public-encrypt": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
|
@ -8947,9 +8976,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"spdx-license-ids": {
|
"spdx-license-ids": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz",
|
||||||
"integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA=="
|
"integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q=="
|
||||||
},
|
},
|
||||||
"spdy": {
|
"spdy": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
|
@ -9838,6 +9867,14 @@
|
||||||
"babel-helper-vue-jsx-merge-props": "^2.0.3"
|
"babel-helper-vue-jsx-merge-props": "^2.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"vue-cropperjs": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-cropperjs/-/vue-cropperjs-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-9x9HFUN2RPpz+AJ9bvnvbtAytW5Q9WeZcMgXqvaRutaCFNGA29rKkKWpUPRy+gn/rFJ5ZpcC1qZO9Hrd3WeqZA==",
|
||||||
|
"requires": {
|
||||||
|
"cropperjs": "^1.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"vue-functional-data-merge": {
|
"vue-functional-data-merge": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/vue-functional-data-merge/-/vue-functional-data-merge-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/vue-functional-data-merge/-/vue-functional-data-merge-3.1.0.tgz",
|
||||||
|
|
BIN
public/css/app.css
vendored
BIN
public/css/app.css
vendored
Binary file not shown.
BIN
public/css/appdark.css
vendored
BIN
public/css/appdark.css
vendored
Binary file not shown.
BIN
public/css/landing.css
vendored
BIN
public/css/landing.css
vendored
Binary file not shown.
BIN
public/js/ace.js
vendored
BIN
public/js/ace.js
vendored
Binary file not shown.
BIN
public/js/collectioncompose.js
vendored
BIN
public/js/collectioncompose.js
vendored
Binary file not shown.
BIN
public/js/collections.js
vendored
BIN
public/js/collections.js
vendored
Binary file not shown.
BIN
public/js/compose-classic.js
vendored
Normal file
BIN
public/js/compose-classic.js
vendored
Normal file
Binary file not shown.
BIN
public/js/compose.js
vendored
BIN
public/js/compose.js
vendored
Binary file not shown.
BIN
public/js/developers.js
vendored
BIN
public/js/developers.js
vendored
Binary file not shown.
BIN
public/js/discover.js
vendored
BIN
public/js/discover.js
vendored
Binary file not shown.
BIN
public/js/hashtag.js
vendored
BIN
public/js/hashtag.js
vendored
Binary file not shown.
BIN
public/js/loops.js
vendored
BIN
public/js/loops.js
vendored
Binary file not shown.
BIN
public/js/mode-dot.js
vendored
BIN
public/js/mode-dot.js
vendored
Binary file not shown.
BIN
public/js/profile.js
vendored
BIN
public/js/profile.js
vendored
Binary file not shown.
BIN
public/js/quill.js
vendored
BIN
public/js/quill.js
vendored
Binary file not shown.
BIN
public/js/search.js
vendored
BIN
public/js/search.js
vendored
Binary file not shown.
BIN
public/js/status.js
vendored
BIN
public/js/status.js
vendored
Binary file not shown.
BIN
public/js/theme-monokai.js
vendored
BIN
public/js/theme-monokai.js
vendored
Binary file not shown.
BIN
public/js/timeline.js
vendored
BIN
public/js/timeline.js
vendored
Binary file not shown.
BIN
public/js/vendor.js
vendored
BIN
public/js/vendor.js
vendored
Binary file not shown.
Binary file not shown.
|
@ -1,197 +1,244 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<input type="file" name="media" class="d-none file-input" multiple="" v-bind:accept="config.uploader.media_types">
|
<input type="file" id="pf-dz" name="media" class="w-100 h-100 d-none file-input" draggable="true" multiple="true" v-bind:accept="config.uploader.media_types">
|
||||||
<div class="timeline">
|
<div class="timeline">
|
||||||
<div class="card status-card card-md-rounded-0">
|
<div v-if="uploading">
|
||||||
<div class="card-header d-inline-flex align-items-center bg-white">
|
<div class="card status-card card-md-rounded-0 w-100 h-100 bg-light py-5" style="border-bottom: 1px solid #f1f1f1">
|
||||||
<img v-bind:src="profile.avatar" width="32px" height="32px" style="border-radius: 32px;" class="box-shadow">
|
<div class="p-5 mt-2">
|
||||||
<a class="username font-weight-bold pl-2 text-dark" v-bind:href="profile.url">
|
<b-progress :value="uploadProgress" :max="100" striped :animated="true"></b-progress>
|
||||||
{{profile.username}}
|
<p class="text-center mb-0 font-weight-bold">Uploading ... ({{uploadProgress}}%)</p>
|
||||||
</a>
|
|
||||||
<div class="text-right" style="flex-grow:1;">
|
|
||||||
<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 fa-lg text-muted"></span>
|
|
||||||
</button>
|
|
||||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
|
|
||||||
<div class="dropdown-item small font-weight-bold" v-on:click="createCollection">Create Collection</div>
|
|
||||||
<div class="dropdown-divider"></div>
|
|
||||||
<div class="dropdown-item small font-weight-bold" v-on:click="about">About</div>
|
|
||||||
<div class="dropdown-item small font-weight-bold" v-on:click="closeModal">Close</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="postPresenterContainer">
|
<div v-else>
|
||||||
<div v-if="uploading">
|
<div class="card status-card card-md-rounded-0 w-100 h-100" style="display:flex;">
|
||||||
<div class="w-100 h-100 bg-light py-5" style="border-bottom: 1px solid #f1f1f1">
|
<div class="card-header d-inline-flex align-items-center bg-white">
|
||||||
<div class="p-5">
|
<div>
|
||||||
<b-progress :value="uploadProgress" :max="100" striped :animated="true"></b-progress>
|
<a v-if="page == 1" href="#" @click.prevent="closeModal()" class="font-weight-bold text-decoration-none text-muted">
|
||||||
<p class="text-center mb-0 font-weight-bold">Uploading ... ({{uploadProgress}}%)</p>
|
<i class="fas fa-times fa-lg"></i>
|
||||||
</div>
|
<span class="font-weight-bold mb-0">{{pageTitle}}</span>
|
||||||
|
</a>
|
||||||
|
<span v-else>
|
||||||
|
<span>
|
||||||
|
<a class="text-lighter text-decoration-none mr-3" href="#" @click.prevent="goBack()"><i class="fas fa-long-arrow-alt-left fa-lg"></i></a>
|
||||||
|
</span>
|
||||||
|
<span class="font-weight-bold mb-0">{{pageTitle}}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-right" style="flex-grow:1;">
|
||||||
|
<!-- <a v-if="page > 1" class="font-weight-bold text-decoration-none" href="#" @click.prevent="page--">Back</a> -->
|
||||||
|
<span v-if="pageLoading">
|
||||||
|
<div class="spinner-border spinner-border-sm" role="status">
|
||||||
|
<span class="sr-only">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<a v-if="!pageLoading && (page > 1 && page <= 3) || (page == 1 && ids.length != 0)" class="font-weight-bold text-decoration-none" href="#" @click.prevent="nextPage">Next</a>
|
||||||
|
<a v-if="!pageLoading && page == 4" class="font-weight-bold text-decoration-none" href="#" @click.prevent="compose">Post</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div class="card-body p-0 border-top">
|
||||||
<div v-if="ids.length > 0 && ids.length != config.uploader.album_limit" class="card-header py-2 bg-primary m-2 rounded cursor-pointer" v-on:click="addMedia($event)">
|
<div v-if="page == 1" class="w-100 h-100 d-flex justify-content-center align-items-center" style="min-height: 400px;">
|
||||||
<p class="text-center mb-0 font-weight-bold text-white"><i class="fas fa-plus mr-1"></i> Add Photo</p>
|
<div class="text-center">
|
||||||
</div>
|
<p>
|
||||||
<div v-if="ids.length == 0" class="w-100 h-100 bg-light py-5 cursor-pointer" style="border-bottom: 1px solid #f1f1f1" v-on:click="addMedia($event)">
|
<a class="btn btn-primary font-weight-bold" href="/i/compose">Compose Post</a>
|
||||||
<div class="p-5">
|
</p>
|
||||||
<p class="text-center font-weight-bold">{{composeMessage()}}</p>
|
<hr>
|
||||||
<p class="text-muted mb-0 small text-center">Accepted Formats: <b>{{acceptedFormats()}}</b></p>
|
<p>
|
||||||
<p class="text-muted mb-0 small text-center">Max File Size: <b>{{maxSize()}}</b></p>
|
<button type="button" class="btn btn-outline-primary font-weight-bold" @click.prevent="addMedia">Compose Post <sup>BETA</sup></button>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<button class="btn btn-outline-primary font-weight-bold" @click.prevent="createCollection">New Collection</button>
|
||||||
|
</p>
|
||||||
|
<!-- <p>
|
||||||
|
<button class="btn btn-outline-primary font-weight-bold" @click.prevent="showAddToStoryCard()">Add To My Story</button>
|
||||||
|
</p> -->
|
||||||
|
<p>
|
||||||
|
<a class="font-weight-bold" href="/site/help">Need Help?</a>
|
||||||
|
</p>
|
||||||
|
<p class="text-muted mb-0 small text-center">Formats: <b>{{acceptedFormats()}}</b> up to <b>{{maxSize()}}</b></p>
|
||||||
<p class="text-muted mb-0 small text-center">Albums can contain up to <b>{{config.uploader.album_limit}}</b> photos or videos</p>
|
<p class="text-muted mb-0 small text-center">Albums can contain up to <b>{{config.uploader.album_limit}}</b> photos or videos</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="ids.length > 0">
|
|
||||||
|
|
||||||
<b-carousel id="p-carousel"
|
<div v-if="page == 2" class="w-100 h-100">
|
||||||
style="text-shadow: 1px 1px 2px #333;"
|
<div v-if="ids.length > 0">
|
||||||
controls
|
<vue-cropper
|
||||||
indicators
|
ref="cropper"
|
||||||
background="#ffffff"
|
:relativeZoom="cropper.zoom"
|
||||||
:interval="0"
|
:aspectRatio="cropper.aspectRatio"
|
||||||
v-model="carouselCursor"
|
:viewMode="cropper.viewMode"
|
||||||
|
:zoomable="cropper.zoomable"
|
||||||
|
:rotatable="true"
|
||||||
|
:src="media[0].url"
|
||||||
|
>
|
||||||
|
</vue-cropper>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="page == 3" class="w-100 h-100">
|
||||||
|
<div slot="img" style="display:flex;min-height: 420px;align-items: center;">
|
||||||
|
<img :class="'d-block img-fluid w-100 ' + [media[carouselCursor].filter_class?media[carouselCursor].filter_class:'']" :src="media[carouselCursor].url" :alt="media[carouselCursor].description" :title="media[carouselCursor].description">
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div v-if="ids.length > 0 && media[carouselCursor].type == 'Image'" class="align-items-center px-2 pt-2">
|
||||||
|
<ul class="nav media-drawer-filters text-center">
|
||||||
|
<li class="nav-item">
|
||||||
|
<div class="p-1 pt-3">
|
||||||
|
<img :src="media[carouselCursor].url" width="100px" height="60px" v-on:click.prevent="toggleFilter($event, null)" class="cursor-pointer">
|
||||||
|
</div>
|
||||||
|
<a :class="[media[carouselCursor].filter_class == null ? 'nav-link text-primary active' : 'nav-link text-muted']" href="#" v-on:click.prevent="toggleFilter($event, null)">No Filter</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" v-for="(filter, index) in filters">
|
||||||
|
<div class="p-1 pt-3">
|
||||||
|
<img :src="media[carouselCursor].url" width="100px" height="60px" :class="filter[1]" v-on:click.prevent="toggleFilter($event, filter[1])">
|
||||||
|
</div>
|
||||||
|
<a :class="[media[carouselCursor].filter_class == filter[1] ? 'nav-link text-primary active' : 'nav-link text-muted']" href="#" v-on:click.prevent="toggleFilter($event, filter[1])">{{filter[0]}}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="page == 4" class="w-100 h-100">
|
||||||
|
<div class="border-bottom mt-2">
|
||||||
|
<div class="media px-3">
|
||||||
|
<img :src="media[0].url" width="42px" height="42px" :class="[media[0].filter_class?'mr-2 ' + media[0].filter_class:'mr-2']">
|
||||||
|
<div class="media-body">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="font-weight-bold text-muted small d-none">Caption</label>
|
||||||
|
<textarea class="form-control border-0 rounded-0 no-focus" rows="2" placeholder="Write a caption..." style="resize:none" v-model="composeText"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="border-bottom">
|
||||||
|
<p class="px-4 mb-0 py-2 cursor-pointer" @click="showTagCard()">Tag people</p>
|
||||||
|
</div>
|
||||||
|
<div class="border-bottom">
|
||||||
|
<p class="px-4 mb-0 py-2 cursor-pointer" @click="showLocationCard()" v-if="!place">Add location</p>
|
||||||
|
<p v-else class="px-4 mb-0 py-2">
|
||||||
|
<span class="text-lighter">Location:</span> {{place.name}}, {{place.country}}
|
||||||
|
<span class="float-right">
|
||||||
|
<a href="#" @click.prevent="showLocationCard()" class="text-muted font-weight-bold small mr-2">Change</a>
|
||||||
|
<a href="#" @click.prevent="place = false" class="text-muted font-weight-bold small">Remove</a>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="border-bottom">
|
||||||
|
<p class="px-4 mb-0 py-2">
|
||||||
|
<span class="text-lighter">Visibility:</span> {{visibilityTag}}
|
||||||
|
<span class="float-right">
|
||||||
|
<a href="#" @click.prevent="showVisibilityCard()" class="text-muted font-weight-bold small mr-2">Change</a>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div style="min-height: 200px;">
|
||||||
|
<p class="px-4 mb-0 py-2 small font-weight-bold text-muted cursor-pointer" @click="showAdvancedSettingsCard()">Advanced settings</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="page == 'tagPeople'" class="w-100 h-100 p-3">
|
||||||
|
<p class="text-center lead text-muted mb-0 py-5">This feature is not available yet.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="page == 'addLocation'" class="w-100 h-100 p-3">
|
||||||
|
<p class="mb-0">Add Location</p>
|
||||||
|
<autocomplete
|
||||||
|
:search="locationSearch"
|
||||||
|
placeholder="Search locations ..."
|
||||||
|
aria-label="Search locations ..."
|
||||||
|
:get-result-value="getResultValue"
|
||||||
|
@submit="onSubmitLocation"
|
||||||
>
|
>
|
||||||
<b-carousel-slide v-if="ids.length > 0" v-for="(preview, index) in media" :key="'preview_media_'+index">
|
</autocomplete>
|
||||||
<div slot="img" :class="[media[index].filter_class?media[index].filter_class:'']" style="display:flex;min-height: 320px;align-items: center;">
|
|
||||||
<img class="d-block img-fluid w-100" :src="preview.url" :alt="preview.description" :title="preview.description">
|
|
||||||
</div>
|
|
||||||
</b-carousel-slide>
|
|
||||||
</b-carousel>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-if="ids.length > 0 && media[carouselCursor].type == 'Image'" class="bg-dark align-items-center">
|
|
||||||
<ul class="nav media-drawer-filters text-center">
|
|
||||||
<li class="nav-item">
|
|
||||||
<div class="p-1 pt-3">
|
|
||||||
<img :src="media[carouselCursor].url" width="100px" height="60px" v-on:click.prevent="toggleFilter($event, null)" class="cursor-pointer">
|
|
||||||
</div>
|
|
||||||
<a :class="[media[carouselCursor].filter_class == null ? 'nav-link text-white active' : 'nav-link text-muted']" href="#" v-on:click.prevent="toggleFilter($event, null)">No Filter</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" v-for="(filter, index) in filters">
|
|
||||||
<div class="p-1 pt-3">
|
|
||||||
<img :src="media[carouselCursor].url" width="100px" height="60px" :class="filter[1]" v-on:click.prevent="toggleFilter($event, filter[1])">
|
|
||||||
</div>
|
|
||||||
<a :class="[media[carouselCursor].filter_class == filter[1] ? 'nav-link text-white active' : 'nav-link text-muted']" href="#" v-on:click.prevent="toggleFilter($event, filter[1])">{{filter[0]}}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="ids.length > 0 && ['Image', 'Video'].indexOf(media[carouselCursor].type) != -1" class="bg-lighter p-2 row">
|
|
||||||
<div v-if="media[carouselCursor].type == 'Image'" class="col-12">
|
|
||||||
<div class="form-group">
|
|
||||||
<input type="text" class="form-control" v-model="media[carouselCursor].alt" placeholder="Optional image description">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div v-if="page == 'advancedSettings'" class="w-100 h-100">
|
||||||
<input type="text" class="form-control" v-model="media[carouselCursor].license" placeholder="Optional media license">
|
<div class="list-group list-group-flush">
|
||||||
|
<div class="list-group-item d-flex justify-content-between">
|
||||||
|
<div>
|
||||||
|
<div class="text-dark ">Turn off commenting</div>
|
||||||
|
<p class="text-muted small mb-0">Disables comments for this post, you can change this later.</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="custom-control custom-switch" style="z-index: 9999;">
|
||||||
|
<input type="checkbox" class="custom-control-input" id="asdisablecomments" v-model="commentsDisabled">
|
||||||
|
<label class="custom-control-label" for="asdisablecomments"></label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="list-group-item d-flex justify-content-between">
|
||||||
|
<div>
|
||||||
|
<div class="text-dark ">Contains NSFW Media</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="custom-control custom-switch" style="z-index: 9999;">
|
||||||
|
<input type="checkbox" class="custom-control-input" id="asnsfw" v-model="nsfw">
|
||||||
|
<label class="custom-control-label" for="asnsfw"></label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a class="list-group-item" @click.prevent="page = 'altText'">
|
||||||
|
<div class="text-dark">Write alt text</div>
|
||||||
|
<p class="text-muted small mb-0">Alt text describes your photos for people with visual impairments.</p>
|
||||||
|
</a>
|
||||||
|
<a href="#" class="list-group-item" @click.prevent="page = 'addToCollection'">
|
||||||
|
<div class="text-dark">Add to Collection</div>
|
||||||
|
<p class="text-muted small mb-0">Add this post to a collection.</p>
|
||||||
|
</a>
|
||||||
|
<a href="#" class="list-group-item" @click.prevent="page = 'schedulePost'">
|
||||||
|
<div class="text-dark">Schedule</div>
|
||||||
|
<p class="text-muted small mb-0">Schedule post for a future date.</p>
|
||||||
|
</a>
|
||||||
|
<a href="#" class="list-group-item" @click.prevent="page = 'mediaMetadata'">
|
||||||
|
<div class="text-dark">Metadata</div>
|
||||||
|
<p class="text-muted small mb-0">Manage media exif and metadata.</p>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="col-6 pt-2">
|
|
||||||
<button class="btn btn-outline-secondary btn-sm mr-1"><i class="fas fa-map-marker-alt"></i></button>
|
<div v-if="page == 'visibility'" class="w-100 h-100">
|
||||||
<button class="btn btn-outline-secondary btn-sm"><i class="fas fa-tools"></i></button>
|
<div class="list-group list-group-flush">
|
||||||
</div> -->
|
<div :class="'list-group-item lead cursor-pointer ' + [visibility == 'public'?'text-primary':'']" @click="toggleVisibility('public')">Public</div>
|
||||||
<div class="col-12 text-right pt-2">
|
<div :class="'list-group-item lead cursor-pointer ' + [visibility == 'unlisted'?'text-primary':'']" @click="toggleVisibility('unlisted')">Unlisted</div>
|
||||||
<button class="btn btn-outline-danger btn-sm font-weight-bold mr-1" v-on:click="deleteMedia()">Delete Media</button>
|
<div :class="'list-group-item lead cursor-pointer ' + [visibility == 'private'?'text-primary':'']" @click="toggleVisibility('private')">Followers Only</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card-body p-0 border-top">
|
<div v-if="page == 'altText'" class="w-100 h-100 p-3">
|
||||||
<div class="caption">
|
<p class="text-center lead text-muted mb-0 py-5">This feature is not available yet.</p>
|
||||||
<textarea class="form-control mb-0 border-0 rounded-0" rows="3" placeholder="Add an optional caption" v-model="composeText"></textarea>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card-footer">
|
<div v-if="page == 'addToCollection'" class="w-100 h-100 p-3">
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<p class="text-center lead text-muted mb-0 py-5">This feature is not available yet.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="page == 'schedulePost'" class="w-100 h-100 p-3">
|
||||||
|
<p class="text-center lead text-muted mb-0 py-5">This feature is not available yet.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="page == 'mediaMetadata'" class="w-100 h-100 p-3">
|
||||||
|
<p class="text-center lead text-muted mb-0 py-5">This feature is not available yet.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="page == 'addToStory'" class="w-100 h-100 p-3">
|
||||||
|
<p class="text-center lead text-muted mb-0 py-5">This feature is not available yet.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- card-footers -->
|
||||||
|
<div v-if="page == 2" class="card-footer bg-white d-flex justify-content-between">
|
||||||
<div>
|
<div>
|
||||||
<div class="custom-control custom-switch d-inline mr-3">
|
<button type="button" class="btn btn-outline-secondary" @click="rotate"><i class="fas fa-undo"></i></button>
|
||||||
<input type="checkbox" class="custom-control-input" id="nsfwToggle" v-model="nsfw">
|
|
||||||
<label class="custom-control-label small font-weight-bold text-muted pt-1" for="nsfwToggle">NSFW</label>
|
|
||||||
</div>
|
|
||||||
<div class="dropdown d-inline">
|
|
||||||
<button class="btn btn-outline-secondary btn-sm py-0 dropdown-toggle" type="button" id="visibility" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
|
||||||
{{visibility[0].toUpperCase() + visibility.slice(1)}}
|
|
||||||
</button>
|
|
||||||
<div class="dropdown-menu" aria-labelledby="visibility" style="width: 200px;">
|
|
||||||
<a :class="[visibility=='public'?'dropdown-item active':'dropdown-item']" href="#" data-id="public" data-title="Public" v-on:click.prevent="visibility = 'public'">
|
|
||||||
<div class="row">
|
|
||||||
<div class="d-none d-block-sm col-sm-2 px-0 text-center">
|
|
||||||
<i class="fas fa-globe"></i>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-sm-10 pl-2">
|
|
||||||
<p class="font-weight-bold mb-0">Public</p>
|
|
||||||
<p class="small mb-0">Anyone can see</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<a :class="[visibility=='private'?'dropdown-item active':'dropdown-item']" href="#" data-id="private" data-title="Followers Only" v-on:click.prevent="visibility = 'private'">
|
|
||||||
<div class="row">
|
|
||||||
<div class="d-none d-block-sm col-sm-2 px-0 text-center">
|
|
||||||
<i class="fas fa-lock"></i>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-sm-10 pl-2">
|
|
||||||
<p class="font-weight-bold mb-0">Followers Only</p>
|
|
||||||
<p class="small mb-0">Only followers can see</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<a :class="[visibility=='unlisted'?'dropdown-item active':'dropdown-item']" href="#" data-id="private" data-title="Unlisted" v-on:click.prevent="visibility = 'unlisted'">
|
|
||||||
<div class="row">
|
|
||||||
<div class="d-none d-block-sm col-sm-2 px-0 text-center">
|
|
||||||
<i class="fas fa-lock"></i>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-sm-10 pl-2">
|
|
||||||
<p class="font-weight-bold mb-0">Unlisted</p>
|
|
||||||
<p class="small mb-0">Not listed on public timelines</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<!-- <a class="dropdown-item" href="#" data-id="circle" data-title="Circle">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-12 col-sm-2 px-0 text-center">
|
|
||||||
<i class="far fa-circle"></i>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-sm-10 pl-2">
|
|
||||||
<p class="font-weight-bold mb-0">Circle</p>
|
|
||||||
<p class="small mb-0">Select a circle</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item" href="#" data-id="direct" data-title="Direct Message">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-12 col-sm-2 px-0 text-center">
|
|
||||||
<i class="fas fa-envelope"></i>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-sm-10 pl-2">
|
|
||||||
<p class="font-weight-bold mb-0">Direct Message</p>
|
|
||||||
<p class="small mb-0">Recipients only</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="small text-muted font-weight-bold">
|
<div>
|
||||||
{{composeText.length}} / {{config.uploader.max_caption_length}}
|
<div class="d-inline-block button-group">
|
||||||
</div>
|
<button :class="'btn font-weight-bold ' + [cropper.aspectRatio == 16/9 ? 'btn-primary':'btn-light']" @click.prevent="changeAspect(16/9)">16:9</button>
|
||||||
<div class="pl-md-5">
|
<button :class="'btn font-weight-bold ' + [cropper.aspectRatio == 4/3 ? 'btn-primary':'btn-light']" @click.prevent="changeAspect(4/3)">4:3</button>
|
||||||
<!-- <div class="btn-group">
|
<button :class="'btn font-weight-bold ' + [cropper.aspectRatio == 3/2 ? 'btn-primary':'btn-light']" @click.prevent="changeAspect(3/2)">3:2</button>
|
||||||
<button type="button" class="btn btn-primary btn-sm font-weight-bold" v-on:click="compose()">{{composeState[0].toUpperCase() + composeState.slice(1)}}</button>
|
<button :class="'btn font-weight-bold ' + [cropper.aspectRatio == 1 ? 'btn-primary':'btn-light']" @click.prevent="changeAspect(1)">1:1</button>
|
||||||
<button type="button" class="btn btn-primary btn-sm dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
<button :class="'btn font-weight-bold ' + [cropper.aspectRatio == 2/3 ? 'btn-primary':'btn-light']" @click.prevent="changeAspect(2/3)">2:3</button>
|
||||||
<span class="sr-only">Toggle Dropdown</span>
|
</div>
|
||||||
</button>
|
|
||||||
<div class="dropdown-menu dropdown-menu-right">
|
|
||||||
<a :class="[composeState == 'publish' ?'dropdown-item font-weight-bold active':'dropdown-item font-weight-bold ']" href="#" v-on:click.prevent="composeState = 'publish'">Publish now</a>
|
|
||||||
<!- - <a :class="[composeState == 'draft' ?'dropdown-item font-weight-bold active':'dropdown-item font-weight-bold ']" href="#" v-on:click.prevent="composeState = 'draft'">Save as draft</a>
|
|
||||||
<a :class="[composeState == 'schedule' ?'dropdown-item font-weight-bold active':'dropdown-item font-weight-bold ']" href="#" v-on:click.prevent="composeState = 'schedule'">Schedule for later</a>
|
|
||||||
<div class="dropdown-divider"></div>
|
|
||||||
<a :class="[composeState == 'delete' ?'dropdown-item font-weight-bold active':'dropdown-item font-weight-bold ']" href="#" v-on:click.prevent="composeState = 'delete'">Delete</a> - ->
|
|
||||||
</div>
|
|
||||||
</div> -->
|
|
||||||
<button class="btn btn-primary btn-sm font-weight-bold px-3" v-on:click="compose()">Publish</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -219,12 +266,36 @@
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.no-focus {
|
||||||
|
border-color: none;
|
||||||
|
outline: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
a.list-group-item {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
a.list-group-item:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
background-color: #f8f9fa !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
import VueCropper from 'vue-cropperjs';
|
||||||
|
import 'cropperjs/dist/cropper.css';
|
||||||
|
import Autocomplete from '@trevoreyre/autocomplete-vue'
|
||||||
|
import '@trevoreyre/autocomplete-vue/dist/style.css'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
VueCropper,
|
||||||
|
Autocomplete
|
||||||
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
config: window.App.config,
|
config: window.App.config,
|
||||||
|
pageLoading: false,
|
||||||
profile: {},
|
profile: {},
|
||||||
composeText: '',
|
composeText: '',
|
||||||
composeTextLength: 0,
|
composeTextLength: 0,
|
||||||
|
@ -233,17 +304,45 @@ export default {
|
||||||
ids: [],
|
ids: [],
|
||||||
media: [],
|
media: [],
|
||||||
carouselCursor: 0,
|
carouselCursor: 0,
|
||||||
visibility: 'public',
|
|
||||||
mediaDrawer: false,
|
|
||||||
composeState: 'publish',
|
|
||||||
uploading: false,
|
uploading: false,
|
||||||
uploadProgress: 0,
|
uploadProgress: 100,
|
||||||
composeType: false
|
composeType: false,
|
||||||
|
page: 1,
|
||||||
|
composeState: 'publish',
|
||||||
|
visibility: 'public',
|
||||||
|
visibilityTag: 'Public',
|
||||||
|
nsfw: false,
|
||||||
|
place: false,
|
||||||
|
commentsDisabled: false,
|
||||||
|
pageTitle: '',
|
||||||
|
|
||||||
|
cropper: {
|
||||||
|
aspectRatio: 1,
|
||||||
|
viewMode: 1,
|
||||||
|
zoomable: true,
|
||||||
|
zoom: 0
|
||||||
|
},
|
||||||
|
|
||||||
|
taggedUsernames: false,
|
||||||
|
namedPages: [
|
||||||
|
'tagPeople',
|
||||||
|
'addLocation',
|
||||||
|
'advancedSettings',
|
||||||
|
'visibility',
|
||||||
|
'altText',
|
||||||
|
'addToCollection',
|
||||||
|
'schedulePost',
|
||||||
|
'mediaMetadata',
|
||||||
|
'addToStory'
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeMount() {
|
beforeMount() {
|
||||||
this.fetchProfile();
|
this.fetchProfile();
|
||||||
|
if(this.config.uploader.media_types.includes('video/mp4') == false) {
|
||||||
|
this.composeType = 'post'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
|
@ -297,6 +396,7 @@ export default {
|
||||||
fetchProfile() {
|
fetchProfile() {
|
||||||
axios.get('/api/v1/accounts/verify_credentials').then(res => {
|
axios.get('/api/v1/accounts/verify_credentials').then(res => {
|
||||||
this.profile = res.data;
|
this.profile = res.data;
|
||||||
|
window.pixelfed.currentUser = res.data;
|
||||||
if(res.data.locked == true) {
|
if(res.data.locked == true) {
|
||||||
this.visibility = 'private';
|
this.visibility = 'private';
|
||||||
}
|
}
|
||||||
|
@ -315,51 +415,88 @@ export default {
|
||||||
|
|
||||||
mediaWatcher() {
|
mediaWatcher() {
|
||||||
let self = this;
|
let self = this;
|
||||||
$(document).on('change', '.file-input', function(e) {
|
self.mediaDragAndDrop();
|
||||||
let io = document.querySelector('.file-input');
|
$(document).on('change', '#pf-dz', function(e) {
|
||||||
Array.prototype.forEach.call(io.files, function(io, i) {
|
self.mediaUpload();
|
||||||
self.uploading = true;
|
|
||||||
if(self.media && self.media.length + i >= self.config.uploader.album_limit) {
|
|
||||||
swal('Error', 'You can only upload ' + self.config.uploader.album_limit + ' photos per album', 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let type = io.type;
|
|
||||||
let acceptedMimes = self.config.uploader.media_types.split(',');
|
|
||||||
let validated = $.inArray(type, acceptedMimes);
|
|
||||||
if(validated == -1) {
|
|
||||||
swal('Invalid File Type', 'The file you are trying to add is not a valid mime type. Please upload a '+self.config.uploader.media_types+' only.', 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let form = new FormData();
|
|
||||||
form.append('file', io);
|
|
||||||
|
|
||||||
let xhrConfig = {
|
|
||||||
onUploadProgress: function(e) {
|
|
||||||
let progress = Math.round( (e.loaded * 100) / e.total );
|
|
||||||
self.uploadProgress = progress;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
axios.post('/api/v1/media', form, xhrConfig)
|
|
||||||
.then(function(e) {
|
|
||||||
self.uploadProgress = 100;
|
|
||||||
self.ids.push(e.data.id);
|
|
||||||
self.media.push(e.data);
|
|
||||||
setTimeout(function() {
|
|
||||||
self.uploading = false;
|
|
||||||
}, 1000);
|
|
||||||
}).catch(function(e) {
|
|
||||||
self.uploading = false;
|
|
||||||
io.value = null;
|
|
||||||
swal('Oops, something went wrong!', 'An unexpected error occurred.', 'error');
|
|
||||||
});
|
|
||||||
io.value = null;
|
|
||||||
self.uploadProgress = 0;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
mediaUpload() {
|
||||||
|
let self = this;
|
||||||
|
self.uploading = true;
|
||||||
|
let io = document.querySelector('#pf-dz');
|
||||||
|
Array.prototype.forEach.call(io.files, function(io, i) {
|
||||||
|
if(self.media && self.media.length + i >= self.config.uploader.album_limit) {
|
||||||
|
swal('Error', 'You can only upload ' + self.config.uploader.album_limit + ' photos per album', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let type = io.type;
|
||||||
|
let acceptedMimes = self.config.uploader.media_types.split(',');
|
||||||
|
let validated = $.inArray(type, acceptedMimes);
|
||||||
|
if(validated == -1) {
|
||||||
|
swal('Invalid File Type', 'The file you are trying to add is not a valid mime type. Please upload a '+self.config.uploader.media_types+' only.', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let form = new FormData();
|
||||||
|
form.append('file', io);
|
||||||
|
|
||||||
|
let xhrConfig = {
|
||||||
|
onUploadProgress: function(e) {
|
||||||
|
let progress = Math.round( (e.loaded * 100) / e.total );
|
||||||
|
self.uploadProgress = progress;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
axios.post('/api/v1/media', form, xhrConfig)
|
||||||
|
.then(function(e) {
|
||||||
|
self.uploadProgress = 100;
|
||||||
|
self.ids.push(e.data.id);
|
||||||
|
self.media.push(e.data);
|
||||||
|
self.page = 2;
|
||||||
|
setTimeout(function() {
|
||||||
|
self.uploading = false;
|
||||||
|
}, 1000);
|
||||||
|
}).catch(function(e) {
|
||||||
|
self.uploading = false;
|
||||||
|
io.value = null;
|
||||||
|
swal('Oops, something went wrong!', 'An unexpected error occurred.', 'error');
|
||||||
|
});
|
||||||
|
io.value = null;
|
||||||
|
self.uploadProgress = 0;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
mediaDragAndDrop() {
|
||||||
|
let self = this;
|
||||||
|
let pdz = document.getElementById('content');
|
||||||
|
|
||||||
|
function allowDrag(e) {
|
||||||
|
e.dataTransfer.dropEffect = 'copy';
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDrop(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
let dz = document.querySelector('#pf-dz');
|
||||||
|
dz.files = e.dataTransfer.files;
|
||||||
|
$('#composeModal').modal('show');
|
||||||
|
self.mediaUpload();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('dragenter', function(e) {
|
||||||
|
});
|
||||||
|
|
||||||
|
pdz.addEventListener('dragenter', allowDrag);
|
||||||
|
pdz.addEventListener('dragover', allowDrag);
|
||||||
|
|
||||||
|
pdz.addEventListener('dragleave', function(e) {
|
||||||
|
//
|
||||||
|
});
|
||||||
|
|
||||||
|
pdz.addEventListener('drop', handleDrop);
|
||||||
|
},
|
||||||
|
|
||||||
toggleFilter(e, filter) {
|
toggleFilter(e, filter) {
|
||||||
this.media[this.carouselCursor].filter_class = filter;
|
this.media[this.carouselCursor].filter_class = filter;
|
||||||
},
|
},
|
||||||
|
@ -446,18 +583,15 @@ export default {
|
||||||
media: this.media,
|
media: this.media,
|
||||||
caption: this.composeText,
|
caption: this.composeText,
|
||||||
visibility: this.visibility,
|
visibility: this.visibility,
|
||||||
cw: this.nsfw
|
cw: this.nsfw,
|
||||||
|
comments_disabled: this.commentsDisabled,
|
||||||
|
place: this.place
|
||||||
};
|
};
|
||||||
axios.post('/api/v2/status/compose', data)
|
axios.post('/api/local/status/compose', data)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
let data = res.data;
|
let data = res.data;
|
||||||
window.location.href = data;
|
window.location.href = data;
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
let res = err.response.data;
|
|
||||||
if(res.message == 'Too Many Attempts.') {
|
|
||||||
swal('You\'re posting too much!', 'We only allow 50 posts per hour or 100 per day. If you\'ve reached that limit, please try again later. If you think this is an error, please contact an administrator.', 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
swal('Oops, something went wrong!', 'An unexpected error occurred.', 'error');
|
swal('Oops, something went wrong!', 'An unexpected error occurred.', 'error');
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
@ -507,6 +641,51 @@ export default {
|
||||||
window.location.href = '/i/collections/create';
|
window.location.href = '/i/collections/create';
|
||||||
},
|
},
|
||||||
|
|
||||||
|
nextPage() {
|
||||||
|
switch(this.page) {
|
||||||
|
case 1:
|
||||||
|
this.page = 3;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
this.pageLoading = true;
|
||||||
|
let self = this;
|
||||||
|
this.$refs.cropper.getCroppedCanvas({
|
||||||
|
maxWidth: 4096,
|
||||||
|
maxHeight: 4096,
|
||||||
|
fillColor: '#fff',
|
||||||
|
imageSmoothingEnabled: false,
|
||||||
|
imageSmoothingQuality: 'high',
|
||||||
|
}).toBlob(function(blob) {
|
||||||
|
let data = new FormData();
|
||||||
|
data.append('file', blob);
|
||||||
|
let url = '/api/local/compose/media/update/' + self.ids[self.carouselCursor];
|
||||||
|
|
||||||
|
axios.post(url, data).then(res => {
|
||||||
|
self.media[self.carouselCursor].url = res.data.url;
|
||||||
|
self.pageLoading = false;
|
||||||
|
self.page++;
|
||||||
|
}).catch(err => {
|
||||||
|
});
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
case 4:
|
||||||
|
this.page++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
rotate() {
|
||||||
|
this.$refs.cropper.rotate(90);
|
||||||
|
},
|
||||||
|
|
||||||
|
changeAspect(ratio) {
|
||||||
|
this.cropper.aspectRatio = ratio;
|
||||||
|
this.$refs.cropper.setAspectRatio(ratio);
|
||||||
|
},
|
||||||
|
|
||||||
maxSize() {
|
maxSize() {
|
||||||
let limit = this.config.uploader.max_photo_size;
|
let limit = this.config.uploader.max_photo_size;
|
||||||
return limit / 1000 + ' MB';
|
return limit / 1000 + ' MB';
|
||||||
|
@ -517,6 +696,75 @@ export default {
|
||||||
return formats.split(',').map(f => {
|
return formats.split(',').map(f => {
|
||||||
return ' ' + f.split('/')[1];
|
return ' ' + f.split('/')[1];
|
||||||
}).toString();
|
}).toString();
|
||||||
|
},
|
||||||
|
|
||||||
|
showTagCard() {
|
||||||
|
this.pageTitle = 'Tag People';
|
||||||
|
this.page = 'tagPeople';
|
||||||
|
},
|
||||||
|
|
||||||
|
showLocationCard() {
|
||||||
|
this.pageTitle = 'Add Location';
|
||||||
|
this.page = 'addLocation';
|
||||||
|
},
|
||||||
|
|
||||||
|
showAdvancedSettingsCard() {
|
||||||
|
this.pageTitle = 'Advanced Settings';
|
||||||
|
this.page = 'advancedSettings';
|
||||||
|
},
|
||||||
|
|
||||||
|
locationSearch(input) {
|
||||||
|
if (input.length < 1) { return []; };
|
||||||
|
let results = [];
|
||||||
|
return axios.get('/api/local/compose/location/search', {
|
||||||
|
params: {
|
||||||
|
q: input
|
||||||
|
}
|
||||||
|
}).then(res => {
|
||||||
|
return res.data;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getResultValue(result) {
|
||||||
|
return result.name + ', ' + result.country
|
||||||
|
},
|
||||||
|
|
||||||
|
onSubmitLocation(result) {
|
||||||
|
this.place = result;
|
||||||
|
this.pageTitle = '';
|
||||||
|
this.page = 4;
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
|
||||||
|
goBack() {
|
||||||
|
this.pageTitle = '';
|
||||||
|
if(this.page == 'addToStory') {
|
||||||
|
this.page = 1;
|
||||||
|
} else {
|
||||||
|
this.namedPages.indexOf(this.page) != -1 ? this.page = 4 : this.page--;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
showVisibilityCard() {
|
||||||
|
this.pageTitle = 'Post Visibility';
|
||||||
|
this.page = 'visibility';
|
||||||
|
},
|
||||||
|
|
||||||
|
showAddToStoryCard() {
|
||||||
|
this.pageTitle = 'Add to Story';
|
||||||
|
this.page = 'addToStory';
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleVisibility(state) {
|
||||||
|
let tags = {
|
||||||
|
public: 'Public',
|
||||||
|
private: 'Followers Only',
|
||||||
|
unlisted: 'Unlisted'
|
||||||
|
}
|
||||||
|
this.visibility = state;
|
||||||
|
this.visibilityTag = tags[state];
|
||||||
|
this.pageTitle = '';
|
||||||
|
this.page = 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
<a v-if="config.ab.loops == true" class="text-decoration-none bg-transparent border border-success rounded d-inline-flex align-items-center justify-content-center mr-3 card-disc" href="/discover/loops">
|
<a v-if="config.ab.loops == true" class="text-decoration-none bg-transparent border border-success rounded d-inline-flex align-items-center justify-content-center mr-3 card-disc" href="/discover/loops">
|
||||||
<p class="text-success lead font-weight-bold mb-0">Loops</p>
|
<p class="text-success lead font-weight-bold mb-0">Loops</p>
|
||||||
</a>
|
</a>
|
||||||
<a v-for="(category, index) in categories" :key="index+'_cat_'" class="bg-dark rounded d-inline-flex align-items-end justify-content-center mr-3 box-shadow card-disc" :href="category.url" :style="'background: linear-gradient(rgba(0, 0, 0, 0.3),rgba(0, 0, 0, 0.3)),url('+category.thumb+');'">
|
<a v-for="(category, index) in categories" :key="index+'_cat_'" class="bg-dark rounded d-inline-flex align-items-end justify-content-center mr-3 box-shadow card-disc text-decoration-none" :href="category.url" :style="'background: linear-gradient(rgba(0, 0, 0, 0.3),rgba(0, 0, 0, 0.3)),url('+category.thumb+');'">
|
||||||
<p class="text-white font-weight-bold" style="text-shadow: 3px 3px 16px #272634;">{{category.name}}</p>
|
<p class="text-white font-weight-bold" style="text-shadow: 3px 3px 16px #272634;">{{category.name}}</p>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|
|
@ -14,14 +14,19 @@
|
||||||
<div class="card card-md-rounded-0 status-container orientation-unknown shadow-none border">
|
<div class="card card-md-rounded-0 status-container orientation-unknown shadow-none border">
|
||||||
<div class="row px-0 mx-0">
|
<div class="row px-0 mx-0">
|
||||||
<div class="d-flex d-md-none align-items-center justify-content-between card-header bg-white w-100">
|
<div class="d-flex d-md-none align-items-center justify-content-between card-header bg-white w-100">
|
||||||
<a :href="statusProfileUrl" class="d-flex align-items-center status-username text-truncate" data-toggle="tooltip" data-placement="bottom" :title="statusUsername">
|
<div class="d-flex">
|
||||||
<div class="status-avatar mr-2">
|
<div class="status-avatar mr-2" @click="redirect(statusProfileUrl)">
|
||||||
<img :src="statusAvatar" width="24px" height="24px" style="border-radius:12px;">
|
<img :src="statusAvatar" width="24px" height="24px" style="border-radius:12px;" class="cursor-pointer">
|
||||||
</div>
|
</div>
|
||||||
<div class="username">
|
<div class="username">
|
||||||
<span class="username-link font-weight-bold text-dark">{{ statusUsername }}</span>
|
<span class="username-link font-weight-bold text-dark cursor-pointer" @click="redirect(statusProfileUrl)">{{ statusUsername }}</span>
|
||||||
|
<span v-if="status.account.is_admin" class="fa-stack" title="Admin Account" data-toggle="tooltip" style="height:1em; line-height:1em; max-width:19px;">
|
||||||
|
<i class="fas fa-certificate text-primary fa-stack-1x"></i>
|
||||||
|
<i class="fas fa-check text-white fa-sm fa-stack-1x" style="font-size:7px;"></i>
|
||||||
|
</span>
|
||||||
|
<p v-if="loaded && status.place != null" class="small mb-0 cursor-pointer text-truncate" style="color:#718096" @click="redirect('/discover/places/' + status.place.id + '/' + status.place.slug)">{{status.place.name}}, {{status.place.country}}</p>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</div>
|
||||||
<div v-if="user != false" class="float-right">
|
<div v-if="user != false" class="float-right">
|
||||||
<div class="post-actions">
|
<div class="post-actions">
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
|
@ -74,14 +79,19 @@
|
||||||
|
|
||||||
<div class="col-12 col-md-4 px-0 d-flex flex-column border-left border-md-left-0">
|
<div class="col-12 col-md-4 px-0 d-flex flex-column border-left border-md-left-0">
|
||||||
<div class="d-md-flex d-none align-items-center justify-content-between card-header py-3 bg-white">
|
<div class="d-md-flex d-none align-items-center justify-content-between card-header py-3 bg-white">
|
||||||
<a :href="statusProfileUrl" class="d-flex align-items-center status-username text-truncate" data-toggle="tooltip" data-placement="bottom" :title="statusUsername">
|
<div class="d-flex align-items-center status-username text-truncate" data-toggle="tooltip" data-placement="bottom" :title="statusUsername">
|
||||||
<div class="status-avatar mr-2">
|
<div class="status-avatar mr-2" @click="redirect(statusProfileUrl)">
|
||||||
<img :src="statusAvatar" width="24px" height="24px" style="border-radius:12px;">
|
<img :src="statusAvatar" width="24px" height="24px" style="border-radius:12px;" class="cursor-pointer">
|
||||||
</div>
|
</div>
|
||||||
<div class="username">
|
<div class="username">
|
||||||
<span class="username-link font-weight-bold text-dark">{{ statusUsername }}</span>
|
<span class="username-link font-weight-bold text-dark cursor-pointer" @click="redirect(statusProfileUrl)">{{ statusUsername }}</span>
|
||||||
|
<span v-if="status.account.is_admin" class="fa-stack" title="Admin Account" data-toggle="tooltip" style="height:1em; line-height:1em; max-width:19px;">
|
||||||
|
<i class="fas fa-certificate text-primary fa-stack-1x"></i>
|
||||||
|
<i class="fas fa-check text-white fa-sm fa-stack-1x" style="font-size:7px;"></i>
|
||||||
|
</span>
|
||||||
|
<p v-if="loaded && status.place != null" class="small mb-0 cursor-pointer text-truncate" style="color:#718096" @click="redirect('/discover/places/' + status.place.id + '/' + status.place.slug)">{{status.place.name}}, {{status.place.country}}</p>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</div>
|
||||||
<div class="float-right">
|
<div class="float-right">
|
||||||
<div class="post-actions">
|
<div class="post-actions">
|
||||||
<div v-if="user != false" class="dropdown">
|
<div v-if="user != false" class="dropdown">
|
||||||
|
@ -183,6 +193,7 @@
|
||||||
<h3 v-bind:class="[reactions.liked ? 'fas fa-heart text-danger pr-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-bind:class="[reactions.liked ? 'fas fa-heart text-danger pr-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 pr-3 m-0 cursor-pointer" title="Comment" v-on:click="replyFocus(status)"></h3>
|
<h3 v-if="!status.comments_disabled" class="far fa-comment pr-3 m-0 cursor-pointer" title="Comment" v-on:click="replyFocus(status)"></h3>
|
||||||
<h3 v-if="status.visibility == 'public'" v-bind:class="[reactions.shared ? 'far fa-share-square pr-3 m-0 text-primary cursor-pointer' : 'far fa-share-square pr-3 m-0 share-btn cursor-pointer']" title="Share" v-on:click="shareStatus"></h3>
|
<h3 v-if="status.visibility == 'public'" v-bind:class="[reactions.shared ? 'far fa-share-square pr-3 m-0 text-primary cursor-pointer' : 'far fa-share-square pr-3 m-0 share-btn cursor-pointer']" title="Share" v-on:click="shareStatus"></h3>
|
||||||
|
<h3 @click="lightbox(status.media_attachments[0])" class="fas fa-expand m-0 cursor-pointer"></h3>
|
||||||
<h3 v-if="status.visibility == 'public'" v-bind:class="[reactions.bookmarked ? 'fas fa-bookmark text-warning m-0 float-right cursor-pointer' : 'far fa-bookmark m-0 float-right cursor-pointer']" title="Bookmark" v-on:click="bookmarkStatus"></h3>
|
<h3 v-if="status.visibility == 'public'" v-bind:class="[reactions.bookmarked ? 'fas fa-bookmark text-warning m-0 float-right cursor-pointer' : 'far fa-bookmark m-0 float-right cursor-pointer']" title="Bookmark" v-on:click="bookmarkStatus"></h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="reaction-counts font-weight-bold mb-0">
|
<div class="reaction-counts font-weight-bold mb-0">
|
||||||
|
@ -266,38 +277,123 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-white">
|
<div class="bg-white">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row py-5">
|
<div class="row pb-5">
|
||||||
<div class="col-12 col-md-8">
|
<div class="col-12 col-md-8 py-4">
|
||||||
<div class="reactions py-2">
|
<div class="reactions d-flex align-items-center">
|
||||||
<h3 v-bind:class="[reactions.liked ? 'fas fa-heart text-danger pr-3 m-0 cursor-pointer' : 'far fa-heart pr-3 m-0 like-btn cursor-pointer']" title="Like" v-on:click="likeStatus"></h3>
|
<div class="text-center mr-5">
|
||||||
<h3 v-if="!status.comments_disabled" class="far fa-comment pr-3 m-0 cursor-pointer" title="Comment" v-on:click="replyFocus(status)"></h3>
|
<div v-bind:class="[reactions.liked ? 'fas fa-heart text-danger m-0 cursor-pointer' : 'far fa-heart m-0 like-btn cursor-pointer']" title="Like" v-on:click="likeStatus" style="font-size:1.575rem">
|
||||||
<h3 v-if="status.visibility == 'public'" v-bind:class="[reactions.shared ? 'far fa-share-square pr-3 m-0 text-primary float-right cursor-pointer' : 'far fa-share-square pr-3 m-0 share-btn float-right cursor-pointer']" title="Share" v-on:click="shareStatus"></h3>
|
</div>
|
||||||
|
<div class="like-count font-weight-bold mt-2 rounded border" style="cursor:pointer;" v-on:click="likesModal">{{status.favourites_count || 0}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<div v-if="status.visibility == 'public'" v-bind:class="[reactions.shared ? 'h3 far fa-share-square m-0 text-primary cursor-pointer' : 'h3 far fa-share-square m-0 share-btn cursor-pointer']" title="Share" v-on:click="shareStatus">
|
||||||
|
</div>
|
||||||
|
<div class="share-count font-weight-bold mt-2 rounded border" v-if="status.visibility == 'public'" style="cursor:pointer;" v-on:click="sharesModal">{{status.reblogs_count || 0}}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="reaction-counts font-weight-bold mb-0">
|
<!-- <div class="reaction-counts font-weight-bold mb-0">
|
||||||
<span style="cursor:pointer;" v-on:click="likesModal">
|
<span class="like-count">{{status.favourites_count || 0}}</span> likes
|
||||||
<span class="like-count">{{status.favourites_count || 0}}</span> likes
|
<span v-if="status.visibility == 'public'" class="float-right" style="cursor:pointer;" v-on:click="sharesModal">
|
||||||
</span>
|
<span class="share-count pl-4">{{status.reblogs_count || 0}}</span> shares
|
||||||
<span v-if="status.visibility == 'public'" class="float-right" style="cursor:pointer;" v-on:click="sharesModal">
|
</span>
|
||||||
<span class="share-count pl-4">{{status.reblogs_count || 0}}</span> shares
|
</div> -->
|
||||||
</span>
|
<div class="media align-items-center mt-3">
|
||||||
</div>
|
<div class="media-body">
|
||||||
<hr>
|
<h2 class="font-weight-bold">
|
||||||
<div class="media align-items-center">
|
{{status.content.length < 100 ? status.content.slice(0,100) : 'Untitled Post'}}
|
||||||
<img :src="statusAvatar" class="rounded-circle shadow-lg mr-3" alt="avatar" width="72px" height="72px">
|
</h2>
|
||||||
<div class="media-body lead">
|
<p class="lead mb-0">
|
||||||
by <a :href="statusProfileUrl">{{statusUsername}}</a>
|
by <a :href="statusProfileUrl">{{statusUsername}}</a>
|
||||||
|
<!-- <span class="px-1 text-lighter">•</span>
|
||||||
|
<a class="font-weight-bold small" href="#">Follow</a> -->
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<img :src="statusAvatar" class="rounded-circle border mr-3" alt="avatar" width="72px" height="72px">
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<div>
|
<div>
|
||||||
<p class="lead"><i class="far fa-clock"></i> {{timestampFormat()}}</p>
|
<p class="lead">
|
||||||
<div class="lead" v-html="status.content"></div>
|
<span v-if="status.place" class="text-truncate">
|
||||||
|
<i class="fas fa-map-marker-alt text-lighter mr-3"></i> {{status.place.name}}, {{status.place.country}}
|
||||||
|
</span>
|
||||||
|
<span v-once class="float-right">
|
||||||
|
<i class="far fa-clock text-lighter mr-3"></i> {{timeAgo(status.created_at)}} ago
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<!-- <div v-if="status.content.length > 100" class="lead" v-html="status.content"></div> -->
|
||||||
|
<!-- <div class="pt-4">
|
||||||
|
<p class="lead">
|
||||||
|
<span class="mr-3"><i class="fas fa-camera text-lighter"></i></span>
|
||||||
|
<span>Nikon D850</span>
|
||||||
|
</p>
|
||||||
|
<p class="lead">
|
||||||
|
<span class="mr-3"><i class="fas fa-ruler-horizontal text-lighter"></i></span>
|
||||||
|
<span>200-500mm</span>
|
||||||
|
</p>
|
||||||
|
<p class="lead">
|
||||||
|
<span class="mr-3"><i class="fas fa-stream text-lighter"></i></span>
|
||||||
|
<span>500mm <span class="px-1"></span> ƒ/7.1 <span class="px-1"></span> 1/1600s <span class="px-1"></span> ISO 800</span>
|
||||||
|
</p>
|
||||||
|
</div> -->
|
||||||
|
<div v-if="status.tags" class="pt-4">
|
||||||
|
<p class="lead">
|
||||||
|
<a v-for="(tag, index) in status.tags" class="btn btn-outline-dark mr-1 mb-1" :href="tag.url + '?src=mp'">{{tag.name}}</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-4">
|
<div class="col-12 col-md-4 pt-4 pl-md-3">
|
||||||
<div v-if="status.comments_disabled" class="bg-light p-5 text-center lead">
|
<p class="lead font-weight-bold">Comments</p>
|
||||||
<p class="mb-0">Comments have been disabled on this post.</p>
|
<div v-if="user" class="moment-comments">
|
||||||
</div>
|
<div class="form-group">
|
||||||
|
<textarea class="form-control" rows="3" placeholder="Add a comment ..." v-model="replyText"></textarea>
|
||||||
|
<p style="padding-top:4px;">
|
||||||
|
<span class="small text-lighter font-weight-bold">
|
||||||
|
{{replyText.length}}/{{config.uploader.max_caption_length}}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
:class="[replyText.length > 1 ? 'btn btn-sm font-weight-bold float-right btn-outline-dark ':'btn btn-sm font-weight-bold float-right btn-outline-lighter']"
|
||||||
|
:disabled="replyText.length < 2"
|
||||||
|
@click="postReply"
|
||||||
|
>Post</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
</div>
|
||||||
|
<div class="comment mt-3" style="max-height: 500px; overflow-y: auto;">
|
||||||
|
<div v-for="(reply, index) in results" :key="'tl' + reply.id + '_' + index" class="media mb-3">
|
||||||
|
<img :src="reply.account.avatar" class="rounded-circle border mr-3" alt="avatar" width="32px" height="32px">
|
||||||
|
<div class="media-body">
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<span class="font-weight-bold">{{reply.account.username}}</span>
|
||||||
|
<span class="small">
|
||||||
|
<a class="text-lighter text-decoration-none" :href="reply.url">{{timeAgo(reply.created_at)}}</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p v-html="reply.content" style="word-break: break-all;"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="media mb-3">
|
||||||
|
<img :src="statusAvatar" class="rounded-circle border mr-3" alt="avatar" width="32px" height="32px">
|
||||||
|
<div class="media-body">
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<span class="font-weight-bold">mona</span>
|
||||||
|
<span class="text-lighter small">2h ago</span>
|
||||||
|
</div>
|
||||||
|
<p>Stunning my friend!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="media mb-3">
|
||||||
|
<img :src="statusAvatar" class="rounded-circle border mr-3" alt="avatar" width="32px" height="32px">
|
||||||
|
<div class="media-body">
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<span class="font-weight-bold">Andre</span>
|
||||||
|
<span class="text-lighter small">3h ago</span>
|
||||||
|
</div>
|
||||||
|
<p>Wow</p>
|
||||||
|
</div>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -378,8 +474,8 @@
|
||||||
size="lg"
|
size="lg"
|
||||||
body-class="p-0"
|
body-class="p-0"
|
||||||
>
|
>
|
||||||
<div v-if="lightboxMedia" :class="lightboxMedia.filter_class">
|
<div v-if="lightboxMedia" >
|
||||||
<img :src="lightboxMedia.url" class="img-fluid" style="min-height: 100%; min-width: 100%">
|
<img :src="lightboxMedia.url" :class="lightboxMedia.filter_class + ' img-fluid'" style="min-height: 100%; min-width: 100%">
|
||||||
</div>
|
</div>
|
||||||
</b-modal>
|
</b-modal>
|
||||||
</div>
|
</div>
|
||||||
|
@ -472,6 +568,7 @@ export default {
|
||||||
],
|
],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
config: window.App.config,
|
||||||
status: false,
|
status: false,
|
||||||
media: {},
|
media: {},
|
||||||
user: false,
|
user: false,
|
||||||
|
@ -787,11 +884,18 @@ export default {
|
||||||
item: this.replyingToId,
|
item: this.replyingToId,
|
||||||
comment: this.replyText
|
comment: this.replyText
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.replyText = '';
|
||||||
|
|
||||||
axios.post('/i/comment', data)
|
axios.post('/i/comment', data)
|
||||||
.then(function(res) {
|
.then(function(res) {
|
||||||
let entity = res.data.entity;
|
let entity = res.data.entity;
|
||||||
if(entity.in_reply_to_id == self.status.id) {
|
if(entity.in_reply_to_id == self.status.id) {
|
||||||
self.results.push(entity);
|
if(self.profileLayout == 'metro') {
|
||||||
|
self.results.push(entity);
|
||||||
|
} else {
|
||||||
|
self.results.unshift(entity);
|
||||||
|
}
|
||||||
let elem = $('.status-comments')[0];
|
let elem = $('.status-comments')[0];
|
||||||
elem.scrollTop = elem.clientHeight;
|
elem.scrollTop = elem.clientHeight;
|
||||||
} else {
|
} else {
|
||||||
|
@ -801,7 +905,6 @@ export default {
|
||||||
el.reply_count = el.reply_count + 1;
|
el.reply_count = el.reply_count + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.replyText = '';
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -847,7 +950,9 @@ export default {
|
||||||
axios.get(url)
|
axios.get(url)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
let self = this;
|
let self = this;
|
||||||
this.results = _.reverse(response.data.data);
|
this.results = this.profileLayout == 'metro' ?
|
||||||
|
_.reverse(response.data.data) :
|
||||||
|
response.data.data;
|
||||||
this.pagination = response.data.meta.pagination;
|
this.pagination = response.data.meta.pagination;
|
||||||
if(this.results.length > 0) {
|
if(this.results.length > 0) {
|
||||||
$('.load-more-link').removeClass('d-none');
|
$('.load-more-link').removeClass('d-none');
|
||||||
|
@ -1054,6 +1159,10 @@ export default {
|
||||||
reply.thread = true;
|
reply.thread = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
redirect(url) {
|
||||||
|
window.location.href = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
|
@ -86,10 +86,9 @@
|
||||||
<div class="profile-details">
|
<div class="profile-details">
|
||||||
<div class="d-none d-md-flex username-bar pb-3 align-items-center">
|
<div class="d-none d-md-flex username-bar pb-3 align-items-center">
|
||||||
<span class="font-weight-ultralight h3 mb-0">{{profile.username}}</span>
|
<span class="font-weight-ultralight h3 mb-0">{{profile.username}}</span>
|
||||||
<span class="pl-1 pb-2" v-if="profile.is_admin" title="Admin Account" data-toggle="tooltip">
|
<span class="pl-1 pb-2 fa-stack" v-if="profile.is_admin" title="Admin Account" data-toggle="tooltip">
|
||||||
<i class="fas fa-certificate fa-lg text-primary">
|
<i class="fas fa-certificate fa-lg text-primary fa-stack-1x"></i>
|
||||||
</i>
|
<i class="fas fa-check text-white fa-sm fa-stack-1x" style="font-size:9px;"></i>
|
||||||
<i class="fas fa-check text-white fa-sm" style="font-size:9px;margin-left: -1.1rem;padding-bottom: 0.6rem;"></i>
|
|
||||||
</span>
|
</span>
|
||||||
<span v-if="profile.id != user.id && user.hasOwnProperty('id')">
|
<span v-if="profile.id != user.id && user.hasOwnProperty('id')">
|
||||||
<span class="pl-4" v-if="relationship.following == true">
|
<span class="pl-4" v-if="relationship.following == true">
|
||||||
|
@ -166,7 +165,7 @@
|
||||||
<div class="row" v-if="mode == 'grid'">
|
<div class="row" v-if="mode == 'grid'">
|
||||||
<div class="col-4 p-1 p-md-3" v-for="(s, index) in timeline">
|
<div class="col-4 p-1 p-md-3" v-for="(s, index) in timeline">
|
||||||
<a class="card info-overlay card-md-border-0" :href="s.url">
|
<a class="card info-overlay card-md-border-0" :href="s.url">
|
||||||
<div class="square">
|
<div :class="[s.sensitive ? 'square' : 'square ' + s.media_attachments[0].filter_class]">
|
||||||
<span v-if="s.pf_type == 'photo:album'" class="float-right mr-3 post-icon"><i class="fas fa-images fa-2x"></i></span>
|
<span v-if="s.pf_type == 'photo:album'" class="float-right mr-3 post-icon"><i class="fas fa-images fa-2x"></i></span>
|
||||||
<span v-if="s.pf_type == 'video'" class="float-right mr-3 post-icon"><i class="fas fa-video fa-2x"></i></span>
|
<span v-if="s.pf_type == 'video'" class="float-right mr-3 post-icon"><i class="fas fa-video fa-2x"></i></span>
|
||||||
<span v-if="s.pf_type == 'video:album'" class="float-right mr-3 post-icon"><i class="fas fa-film fa-2x"></i></span>
|
<span v-if="s.pf_type == 'video:album'" class="float-right mr-3 post-icon"><i class="fas fa-film fa-2x"></i></span>
|
||||||
|
@ -330,7 +329,7 @@
|
||||||
:gutter="{default: '5px'}"
|
:gutter="{default: '5px'}"
|
||||||
>
|
>
|
||||||
<div class="p-1" v-for="(s, index) in timeline">
|
<div class="p-1" v-for="(s, index) in timeline">
|
||||||
<a class="card info-overlay card-md-border-0" :href="s.url">
|
<a :class="[s.sensitive ? 'card info-overlay card-md-border-0' : s.media_attachments[0].filter_class + ' card info-overlay card-md-border-0']" :href="s.url">
|
||||||
<img :src="previewUrl(s)" class="img-fluid w-100">
|
<img :src="previewUrl(s)" class="img-fluid w-100">
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -375,7 +374,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div v-if="following.length == 0" class="list-group-item border-0">
|
<div v-if="following.length == 0" class="list-group-item border-0">
|
||||||
<div class="list-group-item border-0">
|
<div class="list-group-item border-0">
|
||||||
<p class="p-3 text-center mb-0 lead">You are not following anyone.</p>
|
<p class="p-3 text-center mb-0 lead"></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="followingMore" class="list-group-item text-center" v-on:click="followingLoadMore()">
|
<div v-if="followingMore" class="list-group-item text-center" v-on:click="followingLoadMore()">
|
||||||
|
@ -571,6 +570,17 @@
|
||||||
this.mode = u.get('t');
|
this.mode = u.get('t');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
let u = new URLSearchParams(window.location.search);
|
||||||
|
if(u.has('md') && u.get('md') == 'followers') {
|
||||||
|
this.followersModal();
|
||||||
|
}
|
||||||
|
if(u.has('md') && u.get('md') == 'following') {
|
||||||
|
this.followingModal();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
updated() {
|
updated() {
|
||||||
|
@ -780,33 +790,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchStatusComments(status, card) {
|
|
||||||
axios.get('/api/v2/status/'+status.id+'/replies')
|
|
||||||
.then(res => {
|
|
||||||
let comments = card.querySelectorAll('.comments')[0];
|
|
||||||
let data = res.data;
|
|
||||||
data.forEach(function(i, k) {
|
|
||||||
let username = document.createElement('a');
|
|
||||||
username.classList.add('font-weight-bold');
|
|
||||||
username.classList.add('text-dark');
|
|
||||||
username.classList.add('mr-2');
|
|
||||||
username.setAttribute('href', i.account.url);
|
|
||||||
username.textContent = i.account.username;
|
|
||||||
|
|
||||||
let text = document.createElement('span');
|
|
||||||
text.innerHTML = i.content;
|
|
||||||
|
|
||||||
let comment = document.createElement('p');
|
|
||||||
comment.classList.add('read-more');
|
|
||||||
comment.classList.add('mb-0');
|
|
||||||
comment.appendChild(username);
|
|
||||||
comment.appendChild(text);
|
|
||||||
comments.appendChild(comment);
|
|
||||||
});
|
|
||||||
}).catch(err => {
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
fetchRelationships() {
|
fetchRelationships() {
|
||||||
if(document.querySelectorAll('body')[0].classList.contains('loggedIn') == false) {
|
if(document.querySelectorAll('body')[0].classList.contains('loggedIn') == false) {
|
||||||
return;
|
return;
|
||||||
|
@ -878,7 +861,6 @@
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
unblockProfile(status = null) {
|
unblockProfile(status = null) {
|
||||||
if($('body').hasClass('loggedIn') == false) {
|
if($('body').hasClass('loggedIn') == false) {
|
||||||
return;
|
return;
|
||||||
|
@ -912,54 +894,6 @@
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
commentSubmit(status, $event) {
|
|
||||||
if($('body').hasClass('loggedIn') == false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let id = status.id;
|
|
||||||
let form = $event.target;
|
|
||||||
let input = $(form).find('input[name="comment"]');
|
|
||||||
let comment = input.val();
|
|
||||||
let comments = form.parentElement.parentElement.getElementsByClassName('comments')[0];
|
|
||||||
axios.post('/i/comment', {
|
|
||||||
item: id,
|
|
||||||
comment: comment
|
|
||||||
}).then(res => {
|
|
||||||
input.val('');
|
|
||||||
input.blur();
|
|
||||||
|
|
||||||
let username = document.createElement('a');
|
|
||||||
username.classList.add('font-weight-bold');
|
|
||||||
username.classList.add('text-dark');
|
|
||||||
username.classList.add('mr-2');
|
|
||||||
username.setAttribute('href', this.user.url);
|
|
||||||
username.textContent = this.user.username;
|
|
||||||
|
|
||||||
let text = document.createElement('span');
|
|
||||||
text.innerHTML = comment;
|
|
||||||
|
|
||||||
let wrapper = document.createElement('p');
|
|
||||||
wrapper.classList.add('read-more');
|
|
||||||
wrapper.classList.add('mb-0');
|
|
||||||
wrapper.appendChild(username);
|
|
||||||
wrapper.appendChild(text);
|
|
||||||
comments.insertBefore(wrapper, comments.firstChild);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
statusModal(status) {
|
|
||||||
this.modalStatus = status;
|
|
||||||
this.$refs.statusModalRef.show();
|
|
||||||
},
|
|
||||||
|
|
||||||
masonryOrientation(status) {
|
|
||||||
let o = status.media_attachments[0].orientation;
|
|
||||||
if(!o) {
|
|
||||||
o = 'square';
|
|
||||||
}
|
|
||||||
return o;
|
|
||||||
},
|
|
||||||
|
|
||||||
followProfile() {
|
followProfile() {
|
||||||
if($('body').hasClass('loggedIn') == false) {
|
if($('body').hasClass('loggedIn') == false) {
|
||||||
return;
|
return;
|
||||||
|
@ -986,56 +920,60 @@
|
||||||
|
|
||||||
followingModal() {
|
followingModal() {
|
||||||
if($('body').hasClass('loggedIn') == false) {
|
if($('body').hasClass('loggedIn') == false) {
|
||||||
window.location.href = encodeURI('/login?next=/' + this.profile.username + '/');
|
window.location.href = encodeURI('/login?next=/' + this.profileUsername + '/');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(this.profileSettings.following.list == false) {
|
if(this.profileSettings.following.list == false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(this.following.length > 0) {
|
if(this.followingCursor > 1) {
|
||||||
|
this.$refs.followingModal.show();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
axios.get('/api/v1/accounts/'+this.profileId+'/following', {
|
||||||
|
params: {
|
||||||
|
page: this.followingCursor
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
this.following = res.data;
|
||||||
|
this.followingCursor++;
|
||||||
|
if(res.data.length < 10) {
|
||||||
|
this.followingMore = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
this.$refs.followingModal.show();
|
this.$refs.followingModal.show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
axios.get('/api/v1/accounts/'+this.profile.id+'/following', {
|
|
||||||
params: {
|
|
||||||
page: this.followingCursor
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(res => {
|
|
||||||
this.following = res.data;
|
|
||||||
this.followingCursor++;
|
|
||||||
if(res.data.length < 10) {
|
|
||||||
this.followingMore = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.$refs.followingModal.show();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
followersModal() {
|
followersModal() {
|
||||||
if($('body').hasClass('loggedIn') == false) {
|
if($('body').hasClass('loggedIn') == false) {
|
||||||
window.location.href = encodeURI('/login?next=/' + this.profile.username + '/');
|
window.location.href = encodeURI('/login?next=/' + this.profileUsername + '/');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(this.profileSettings.followers.list == false) {
|
if(this.profileSettings.followers.list == false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(this.followers.length > 0) {
|
if(this.followerCursor > 1) {
|
||||||
|
this.$refs.followerModal.show();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
axios.get('/api/v1/accounts/'+this.profileId+'/followers', {
|
||||||
|
params: {
|
||||||
|
page: this.followerCursor
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
this.followers.push(...res.data);
|
||||||
|
this.followerCursor++;
|
||||||
|
if(res.data.length < 10) {
|
||||||
|
this.followerMore = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
this.$refs.followerModal.show();
|
this.$refs.followerModal.show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
axios.get('/api/v1/accounts/'+this.profile.id+'/followers', {
|
|
||||||
params: {
|
|
||||||
page: this.followerCursor
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(res => {
|
|
||||||
this.followers = res.data;
|
|
||||||
this.followerCursor++;
|
|
||||||
if(res.data.length < 10) {
|
|
||||||
this.followerMore = false;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
this.$refs.followerModal.show();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
followingLoadMore() {
|
followingLoadMore() {
|
||||||
|
|
|
@ -66,15 +66,28 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card mb-sm-4 status-card card-md-rounded-0 shadow-none border">
|
<div class="card mb-sm-4 status-card card-md-rounded-0 shadow-none border">
|
||||||
<div v-if="!modes.distractionFree" class="card-header d-inline-flex align-items-center bg-white">
|
<div v-if="!modes.distractionFree" class="card-header d-inline-flex align-items-center bg-white">
|
||||||
<img v-bind:src="status.account.avatar" width="32px" height="32px" style="border-radius: 32px;">
|
<img v-bind:src="status.account.avatar" width="32px" height="32px" style="border-radius: 32px;">
|
||||||
<a class="username font-weight-bold pl-2 text-dark" v-bind:href="status.account.url">
|
<div class="pl-2">
|
||||||
{{status.account.username}}
|
<!-- <a class="d-block username font-weight-bold text-dark" v-bind:href="status.account.url" style="line-height:0.5;"> -->
|
||||||
</a>
|
<a class="username font-weight-bold text-dark text-decoration-none" v-bind:href="status.account.url">
|
||||||
|
{{status.account.username}}
|
||||||
|
</a>
|
||||||
|
<span v-if="status.account.is_admin" class="fa-stack" title="Admin Account" data-toggle="tooltip" style="height:1em; line-height:1em; max-width:19px;">
|
||||||
|
<i class="fas fa-certificate text-primary fa-stack-1x"></i>
|
||||||
|
<i class="fas fa-check text-white fa-sm fa-stack-1x" style="font-size:7px;"></i>
|
||||||
|
</span>
|
||||||
|
<span v-if="scope != 'home' && status.account.id != profile.id && status.account.relationship">
|
||||||
|
<span class="px-1">•</span>
|
||||||
|
<span :class="'font-weight-bold cursor-pointer ' + [status.account.relationship.following == true ? 'text-muted' : 'text-primary']" @click="followAction(status)">{{status.account.relationship.following == true ? 'Following' : 'Follow'}}</span>
|
||||||
|
</span>
|
||||||
|
<a v-if="status.place" class="d-block small text-decoration-none" :href="'/discover/places/'+status.place.id+'/'+status.place.slug" style="color:#718096">{{status.place.name}}, {{status.place.country}}</a>
|
||||||
|
</div>
|
||||||
<div class="text-right" style="flex-grow:1;">
|
<div class="text-right" style="flex-grow:1;">
|
||||||
<button class="btn btn-link text-dark py-0" type="button" @click="ctxMenu(status)">
|
<button class="btn btn-link text-dark py-0" type="button" @click="ctxMenu(status)">
|
||||||
<span class="fas fa-ellipsis-h text-dark"></span>
|
<span class="fas fa-ellipsis-h text-lighter"></span>
|
||||||
</button>
|
</button>
|
||||||
<!-- <div class="dropdown-menu dropdown-menu-right">
|
<!-- <div class="dropdown-menu dropdown-menu-right">
|
||||||
<a class="dropdown-item font-weight-bold" :href="status.url">Go to post</a>
|
<a class="dropdown-item font-weight-bold" :href="status.url">Go to post</a>
|
||||||
|
@ -114,7 +127,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="postPresenterContainer" v-on:dblclick="likeStatus(status)">
|
<div class="postPresenterContainer" @click="lightbox(status)">
|
||||||
<div v-if="status.pf_type === 'photo'" class="w-100">
|
<div v-if="status.pf_type === 'photo'" class="w-100">
|
||||||
<photo-presenter :status="status" v-on:lightbox="lightbox"></photo-presenter>
|
<photo-presenter :status="status" v-on:lightbox="lightbox"></photo-presenter>
|
||||||
</div>
|
</div>
|
||||||
|
@ -141,10 +154,13 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div v-if="!modes.distractionFree" class="reactions my-1">
|
<div v-if="!modes.distractionFree" class="reactions my-1 pb-2">
|
||||||
<h3 v-bind:class="[status.favourited ? 'fas fa-heart text-danger pr-3 m-0 cursor-pointer' : 'far fa-heart pr-3 m-0 like-btn cursor-pointer']" title="Like" v-on:click="likeStatus(status, $event)"></h3>
|
<h3 v-bind:class="[status.favourited ? 'fas fa-heart text-danger pr-3 m-0 cursor-pointer' : 'far fa-heart pr-3 m-0 like-btn text-lighter cursor-pointer']" title="Like" v-on:click="likeStatus(status, $event)"></h3>
|
||||||
<h3 v-if="!status.comments_disabled" class="far fa-comment pr-3 m-0 cursor-pointer" title="Comment" v-on:click="commentFocus(status, $event)"></h3>
|
<h3 v-if="!status.comments_disabled" class="far fa-comment text-lighter pr-3 m-0 cursor-pointer" title="Comment" v-on:click="commentFocus(status, $event)"></h3>
|
||||||
<h3 v-if="status.visibility == 'public'" v-bind:class="[status.reblogged ? 'far fa-share-square pr-3 m-0 text-primary cursor-pointer' : 'far fa-share-square pr-3 m-0 share-btn cursor-pointer']" title="Share" v-on:click="shareStatus(status, $event)"></h3>
|
<h3 v-if="status.visibility == 'public'" v-bind:class="[status.reblogged ? 'fas fa-retweet pr-3 m-0 text-primary cursor-pointer' : 'fas fa-retweet pr-3 m-0 text-lighter share-btn cursor-pointer']" title="Share" v-on:click="shareStatus(status, $event)"></h3>
|
||||||
|
<span class="float-right">
|
||||||
|
<h3 class="fas fa-expand pr-3 m-0 cursor-pointer text-lighter" v-on:click="lightbox(status)"></h3>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="likes font-weight-bold" v-if="expLc(status) == true && !modes.distractionFree">
|
<div class="likes font-weight-bold" v-if="expLc(status) == true && !modes.distractionFree">
|
||||||
|
@ -165,8 +181,11 @@
|
||||||
<span v-html="reply.content"></span>
|
<span v-html="reply.content"></span>
|
||||||
</span>
|
</span>
|
||||||
<span class="mb-0" style="min-width:38px">
|
<span class="mb-0" style="min-width:38px">
|
||||||
<span v-on:click="likeStatus(reply, $event)"><i v-bind:class="[reply.favourited ? 'fas fa-heart fa-sm text-danger':'far fa-heart fa-sm text-lighter']"></i></span>
|
<span v-on:click="likeStatus(reply, $event)"><i v-bind:class="[reply.favourited ? 'fas fa-heart fa-sm text-danger cursor-pointer':'far fa-heart fa-sm text-lighter cursor-pointer']"></i></span>
|
||||||
<post-menu :status="reply" :profile="profile" size="sm" :modal="'true'" :feed="feed" class="d-inline-flex pl-2"></post-menu>
|
<!-- <post-menu :status="reply" :profile="profile" size="sm" :modal="'true'" :feed="feed" class="d-inline-flex pl-2"></post-menu> -->
|
||||||
|
<span class="text-lighter pl-2 cursor-pointer" @click="ctxMenu(reply)">
|
||||||
|
<span class="fas fa-ellipsis-v text-lighter"></span>
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -209,12 +228,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!loading && feed.length > 0">
|
<div v-if="!loading && feed.length">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<infinite-loading @infinite="infiniteTimeline" :distance="800">
|
<infinite-loading @infinite="infiniteTimeline" :distance="800">
|
||||||
<div slot="no-more" class="font-weight-bold">No more posts to load</div>
|
<div slot="no-more" class="font-weight-bold">No more posts to load</div>
|
||||||
<div slot="no-results" class="font-weight-bold">No posts found</div>
|
<div slot="no-results" class="font-weight-bold">No more posts to load</div>
|
||||||
</infinite-loading>
|
</infinite-loading>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -252,22 +271,36 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="card-footer bg-white py-1 d-none">
|
<div class="card-footer bg-transparent border-0 mt-2 py-1">
|
||||||
<div class="d-flex justify-content-between text-center">
|
<div class="d-flex justify-content-between text-center">
|
||||||
<span class="pl-3 cursor-pointer" v-on:click="redirect(profile.url)">
|
<span class="cursor-pointer" @click="redirect(profile.url)">
|
||||||
<p class="mb-0 font-weight-bold">{{profile.statuses_count}}</p>
|
<p class="mb-0 font-weight-bold">{{profile.statuses_count}}</p>
|
||||||
<p class="mb-0 small text-muted">Posts</p>
|
<p class="mb-0 small text-muted">Posts</p>
|
||||||
</span>
|
</span>
|
||||||
<span class="cursor-pointer" v-on:click="followersModal()">
|
<span class="cursor-pointer" @click="redirect(profile.url+'?md=followers')">
|
||||||
<p class="mb-0 font-weight-bold">{{profile.followers_count}}</p>
|
<p class="mb-0 font-weight-bold">{{profile.followers_count}}</p>
|
||||||
<p class="mb-0 small text-muted">Followers</p>
|
<p class="mb-0 small text-muted">Followers</p>
|
||||||
</span>
|
</span>
|
||||||
<span class="pr-3 cursor-pointer" v-on:click="followingModal()">
|
<span class="cursor-pointer" @click="redirect(profile.url+'?md=following')">
|
||||||
<p class="mb-0 font-weight-bold">{{profile.following_count}}</p>
|
<p class="mb-0 font-weight-bold">{{profile.following_count}}</p>
|
||||||
<p class="mb-0 small text-muted">Following</p>
|
<p class="mb-0 small text-muted">Following</p>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div> -->
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="showTips" class="mb-4 card-tips">
|
||||||
|
<div class="card border shadow-none mb-3" style="max-width: 18rem;">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="card-title">
|
||||||
|
<span class="font-weight-bold">Tip: Hide follower counts</span>
|
||||||
|
<span class="float-right cursor-pointer" @click.prevent="hideTips()"><i class="fas fa-times text-lighter"></i></span>
|
||||||
|
</div>
|
||||||
|
<p class="card-text">
|
||||||
|
<span style="font-size:13px;">You can hide followers or following count and lists on your profile.</span>
|
||||||
|
<br><a href="/settings/privacy/" class="small font-weight-bold">Privacy Settings</a></p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -400,9 +433,25 @@
|
||||||
<!-- <div class="list-group-item rounded cursor-pointer" @click="ctxMenuEmbed()">Embed</div>
|
<!-- <div 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="ctxMenuShare()">Share</div> -->
|
||||||
<div class="list-group-item rounded cursor-pointer" @click="ctxMenuCopyLink()">Copy Link</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>
|
||||||
|
<div v-if="ctxMenuStatus && (profile.is_admin || profile.id == ctxMenuStatus.account.id)" class="list-group-item rounded cursor-pointer" @click="deletePost(ctxMenuStatus)">Delete</div>
|
||||||
<div class="list-group-item rounded cursor-pointer text-lighter" @click="closeCtxMenu()">Cancel</div>
|
<div class="list-group-item rounded cursor-pointer text-lighter" @click="closeCtxMenu()">Cancel</div>
|
||||||
</div>
|
</div>
|
||||||
</b-modal>
|
</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="moderatePost(ctxMenuStatus, 'unlist')">Unlist from Timelines</div>
|
||||||
|
<div class="list-group-item rounded cursor-pointer" @click="">Add Content Warning</div>
|
||||||
|
<div class="list-group-item rounded cursor-pointer text-lighter" @click="ctxModMenuClose()">Cancel</div>
|
||||||
|
</div>
|
||||||
|
</b-modal>
|
||||||
<b-modal ref="ctxShareModal"
|
<b-modal ref="ctxShareModal"
|
||||||
id="ctx-share-modal"
|
id="ctx-share-modal"
|
||||||
title="Share"
|
title="Share"
|
||||||
|
@ -444,8 +493,8 @@
|
||||||
size="lg"
|
size="lg"
|
||||||
body-class="p-0"
|
body-class="p-0"
|
||||||
>
|
>
|
||||||
<div v-if="lightboxMedia" :class="lightboxMedia.filter_class">
|
<div v-if="lightboxMedia" :class="lightboxMedia.filter_class" class="w-100 h-100">
|
||||||
<img :src="lightboxMedia.url" class="img-fluid" style="min-height: 100%; min-width: 100%">
|
<img :src="lightboxMedia.url" style="max-height: 100%; max-width: 100%">
|
||||||
</div>
|
</div>
|
||||||
</b-modal>
|
</b-modal>
|
||||||
</div>
|
</div>
|
||||||
|
@ -524,7 +573,8 @@
|
||||||
ctxMenuStatus: false,
|
ctxMenuStatus: false,
|
||||||
ctxMenuRelationship: false,
|
ctxMenuRelationship: false,
|
||||||
ctxEmbedPayload: false,
|
ctxEmbedPayload: false,
|
||||||
copiedEmbed: false
|
copiedEmbed: false,
|
||||||
|
showTips: true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -563,6 +613,10 @@
|
||||||
this.modes.distractionFree = false;
|
this.modes.distractionFree = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(localStorage.getItem('metro-tips') == 'false') {
|
||||||
|
this.showTips = false;
|
||||||
|
}
|
||||||
|
|
||||||
this.$nextTick(function () {
|
this.$nextTick(function () {
|
||||||
$('[data-toggle="tooltip"]').tooltip()
|
$('[data-toggle="tooltip"]').tooltip()
|
||||||
});
|
});
|
||||||
|
@ -623,13 +677,19 @@
|
||||||
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.fetchHashtagPosts();
|
if(this.feed.length == 6) {
|
||||||
|
this.fetchHashtagPosts();
|
||||||
|
this.fetchTimelineApi();
|
||||||
|
} else {
|
||||||
|
this.fetchHashtagPosts();
|
||||||
|
}
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
infiniteTimeline($state) {
|
infiniteTimeline($state) {
|
||||||
if(this.loading) {
|
if(this.loading) {
|
||||||
|
$state.complete();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let apiUrl = false;
|
let apiUrl = false;
|
||||||
|
@ -649,7 +709,7 @@
|
||||||
axios.get(apiUrl, {
|
axios.get(apiUrl, {
|
||||||
params: {
|
params: {
|
||||||
max_id: this.max_id,
|
max_id: this.max_id,
|
||||||
limit: 6
|
limit: 9
|
||||||
},
|
},
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
if (res.data.length && this.loading == false) {
|
if (res.data.length && this.loading == false) {
|
||||||
|
@ -669,6 +729,9 @@
|
||||||
} else {
|
} else {
|
||||||
$state.complete();
|
$state.complete();
|
||||||
}
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
this.loading = false;
|
||||||
|
$state.complete();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -826,7 +889,7 @@
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
deletePost(status, index) {
|
deletePost(status) {
|
||||||
if($('body').hasClass('loggedIn') == false || this.ownerOrAdmin(status) == false) {
|
if($('body').hasClass('loggedIn') == false || this.ownerOrAdmin(status) == false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -841,8 +904,8 @@
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
this.feed = this.feed.filter(s => {
|
this.feed = this.feed.filter(s => {
|
||||||
return s.id != status.id;
|
return s.id != status.id;
|
||||||
})
|
});
|
||||||
swal('Success', 'You have successfully deleted this post', 'success');
|
this.$refs.ctxModal.hide();
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
swal('Error', 'Something went wrong. Please try again later.', 'error');
|
swal('Error', 'Something went wrong. Please try again later.', 'error');
|
||||||
});
|
});
|
||||||
|
@ -916,6 +979,7 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'unlisted':
|
case 'unlisted':
|
||||||
msg = 'Are you sure you want to unlist from timelines for ' + username + ' ?';
|
msg = 'Are you sure you want to unlist from timelines for ' + username + ' ?';
|
||||||
swal({
|
swal({
|
||||||
|
@ -1073,8 +1137,8 @@
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
lightbox(src) {
|
lightbox(status) {
|
||||||
this.lightboxMedia = src;
|
this.lightboxMedia = status.media_attachments[0];
|
||||||
this.$refs.lightboxModal.show();
|
this.$refs.lightboxModal.show();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1114,13 +1178,26 @@
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
followModalAction(id, index, type = 'following') {
|
followAction(status) {
|
||||||
|
let id = status.account.id;
|
||||||
|
|
||||||
axios.post('/i/follow', {
|
axios.post('/i/follow', {
|
||||||
item: id
|
item: id
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
if(type == 'following') {
|
this.feed.forEach(s => {
|
||||||
this.following.splice(index, 1);
|
if(s.account.id == id) {
|
||||||
|
s.account.relationship.following = !s.account.relationship.following;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let username = status.account.acct;
|
||||||
|
|
||||||
|
if(status.account.relationship.following) {
|
||||||
|
swal('Follow successful!', 'You are now following ' + username, 'success');
|
||||||
|
} else {
|
||||||
|
swal('Unfollow successful!', 'You are no longer following ' + username, 'success');
|
||||||
}
|
}
|
||||||
|
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
if(err.response.data.message) {
|
if(err.response.data.message) {
|
||||||
swal('Error', err.response.data.message, 'error');
|
swal('Error', err.response.data.message, 'error');
|
||||||
|
@ -1190,7 +1267,6 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchHashtagPosts() {
|
fetchHashtagPosts() {
|
||||||
|
|
||||||
axios.get('/api/local/discover/tag/list')
|
axios.get('/api/local/discover/tag/list')
|
||||||
.then(res => {
|
.then(res => {
|
||||||
let tags = res.data;
|
let tags = res.data;
|
||||||
|
@ -1210,7 +1286,6 @@
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
ctxMenu(status) {
|
ctxMenu(status) {
|
||||||
|
@ -1255,9 +1330,16 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
ctxMenuFollow() {
|
ctxMenuFollow() {
|
||||||
|
let id = this.ctxMenuStatus.account.id;
|
||||||
axios.post('/i/follow', {
|
axios.post('/i/follow', {
|
||||||
item: this.ctxMenuStatus.account.id
|
item: id
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
|
this.feed.forEach(s => {
|
||||||
|
if(s.account.id == id) {
|
||||||
|
s.account.relationship.following = !s.account.relationship.following;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let username = this.ctxMenuStatus.account.acct;
|
let username = this.ctxMenuStatus.account.acct;
|
||||||
this.closeCtxMenu();
|
this.closeCtxMenu();
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
|
@ -1267,10 +1349,21 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
ctxMenuUnfollow() {
|
ctxMenuUnfollow() {
|
||||||
|
let id = this.ctxMenuStatus.account.id;
|
||||||
axios.post('/i/follow', {
|
axios.post('/i/follow', {
|
||||||
item: this.ctxMenuStatus.account.id
|
item: id
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
|
this.feed.forEach(s => {
|
||||||
|
if(s.account.id == id) {
|
||||||
|
s.account.relationship.following = !s.account.relationship.following;
|
||||||
|
}
|
||||||
|
});
|
||||||
let username = this.ctxMenuStatus.account.acct;
|
let username = this.ctxMenuStatus.account.acct;
|
||||||
|
if(this.scope == 'home') {
|
||||||
|
this.feed = this.feed.filter(s => {
|
||||||
|
return s.account.id != this.ctxMenuStatus.account.id;
|
||||||
|
});
|
||||||
|
}
|
||||||
this.closeCtxMenu();
|
this.closeCtxMenu();
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
swal('Unfollow successful!', 'You are no longer following ' + username, 'success');
|
swal('Unfollow successful!', 'You are no longer following ' + username, 'success');
|
||||||
|
@ -1300,6 +1393,26 @@
|
||||||
ctxCopyEmbed() {
|
ctxCopyEmbed() {
|
||||||
navigator.clipboard.writeText(this.ctxEmbedPayload);
|
navigator.clipboard.writeText(this.ctxEmbedPayload);
|
||||||
this.$refs.ctxEmbedModal.hide();
|
this.$refs.ctxEmbedModal.hide();
|
||||||
|
},
|
||||||
|
|
||||||
|
ctxModMenuShow() {
|
||||||
|
this.$refs.ctxModal.hide();
|
||||||
|
this.$refs.ctxModModal.show();
|
||||||
|
},
|
||||||
|
|
||||||
|
ctxModMenu() {
|
||||||
|
this.$refs.ctxModal.hide();
|
||||||
|
},
|
||||||
|
|
||||||
|
ctxModMenuClose() {
|
||||||
|
this.$refs.ctxModal.hide();
|
||||||
|
this.$refs.ctxModModal.hide();
|
||||||
|
},
|
||||||
|
|
||||||
|
hideTips() {
|
||||||
|
this.showTips = false;
|
||||||
|
let ls = window.localStorage;
|
||||||
|
ls.setItem('metro-tips', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
4
resources/assets/js/micro.js
vendored
Normal file
4
resources/assets/js/micro.js
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
Vue.component(
|
||||||
|
'micro-ui',
|
||||||
|
require('./components/Micro.vue').default
|
||||||
|
);
|
5
resources/assets/sass/custom.scss
vendored
5
resources/assets/sass/custom.scss
vendored
|
@ -534,6 +534,11 @@ details summary::-webkit-details-marker {
|
||||||
color:#B8C2CC !important;
|
color:#B8C2CC !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-outline-lighter {
|
||||||
|
color: #B8C2CC !important;
|
||||||
|
border-color: #B8C2CC !important;
|
||||||
|
}
|
||||||
|
|
||||||
.cursor-pointer {
|
.cursor-pointer {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ return [
|
||||||
'network' => 'Network',
|
'network' => 'Network',
|
||||||
'discover' => 'Discover',
|
'discover' => 'Discover',
|
||||||
'viewMyProfile' => 'View my profile',
|
'viewMyProfile' => 'View my profile',
|
||||||
|
'myProfile' => 'My Profile',
|
||||||
'myTimeline' => 'My Timeline',
|
'myTimeline' => 'My Timeline',
|
||||||
'publicTimeline' => 'Public Timeline',
|
'publicTimeline' => 'Public Timeline',
|
||||||
'remoteFollow' => 'Remote Follow',
|
'remoteFollow' => 'Remote Follow',
|
||||||
|
|
22
resources/views/discover/places/directory/cities.blade.php
Normal file
22
resources/views/discover/places/directory/cities.blade.php
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="container mt-5">
|
||||||
|
<div class="col-12">
|
||||||
|
<p class="font-weight-bold text-lighter text-uppercase">Cities in {{$places->first()->country}}</p>
|
||||||
|
<div class="card border shadow-none">
|
||||||
|
<div class="card-body row pl-md-5 ml-md-5">
|
||||||
|
@foreach($places as $place)
|
||||||
|
<div class="col-12 col-md-4 mb-2">
|
||||||
|
<a href="{{$place->cityUrl()}}" class="text-dark pr-3 b-3">{{$place->name}}</a>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
<div class="card-footer bg-white pb-0 d-flex justify-content-center">
|
||||||
|
{{$places->links()}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@endsection
|
22
resources/views/discover/places/directory/home.blade.php
Normal file
22
resources/views/discover/places/directory/home.blade.php
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="container mt-5">
|
||||||
|
<div class="col-12">
|
||||||
|
<p class="font-weight-bold text-lighter text-uppercase">Countries</p>
|
||||||
|
<div class="card border shadow-none">
|
||||||
|
<div class="card-body row pl-md-5 ml-md-5">
|
||||||
|
@foreach($places as $place)
|
||||||
|
<div class="col-12 col-md-4 mb-2">
|
||||||
|
<a href="{{$place->countryUrl()}}" class="text-dark pr-3 b-3">{{$place->country}}</a>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
<div class="card-footer bg-white pb-0 d-flex justify-content-center">
|
||||||
|
{{$places->links()}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@endsection
|
48
resources/views/discover/places/show.blade.php
Normal file
48
resources/views/discover/places/show.blade.php
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<div class="profile-header row my-5">
|
||||||
|
<div class="col-12 col-md-2">
|
||||||
|
<div class="profile-avatar">
|
||||||
|
<div class="bg-pixelfed mb-3 d-flex align-items-center justify-content-center display-4 font-weight-bold text-white" style="width: 132px; height: 132px; border-radius: 100%"><i class="fas fa-map-pin"></i></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-9 d-flex align-items-center">
|
||||||
|
<div class="profile-details">
|
||||||
|
<div class="username-bar pb-2 d-flex align-items-center">
|
||||||
|
<div class="ml-4">
|
||||||
|
<p class="h3 font-weight-lighter">{{$place->name}}, {{$place->country}}</p>
|
||||||
|
<p class="small text-muted">({{$place->lat}}, {{$place->long}})</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tag-timeline">
|
||||||
|
<div class="row">
|
||||||
|
@if($posts->count() > 0)
|
||||||
|
@foreach($posts as $status)
|
||||||
|
<div class="col-4 p-0 p-sm-2 p-md-3">
|
||||||
|
<a class="card info-overlay card-md-border-0" href="{{$status->url()}}">
|
||||||
|
<div class="square {{$status->firstMedia()->filter_class}}">
|
||||||
|
<div class="square-content" style="background-image: url('{{$status->thumb()}}')"></div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
@else
|
||||||
|
<div class="col-12 bg-white p-5 border">
|
||||||
|
<p class="lead text-center text-dark mb-0">No results for this location</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script type="text/javascript" src="{{ mix('js/compose.js') }}"></script>
|
||||||
|
<script type="text/javascript">App.boot();</script>
|
||||||
|
@endpush
|
|
@ -7,5 +7,5 @@
|
||||||
@push('scripts')
|
@push('scripts')
|
||||||
<script type="text/javascript" src="{{ mix('js/hashtag.js') }}"></script>
|
<script type="text/javascript" src="{{ mix('js/hashtag.js') }}"></script>
|
||||||
<script type="text/javascript" src="{{ mix('js/compose.js') }}"></script>
|
<script type="text/javascript" src="{{ mix('js/compose.js') }}"></script>
|
||||||
<script type="text/javascript">$(document).ready(function(){new Vue({el: '#content'});});</script>
|
<script type="text/javascript">App.boot();</script>
|
||||||
@endpush
|
@endpush
|
|
@ -8,6 +8,7 @@
|
||||||
<a href="{{route('site.help')}}" class="text-primary pr-3">{{__('site.help')}}</a>
|
<a href="{{route('site.help')}}" class="text-primary pr-3">{{__('site.help')}}</a>
|
||||||
<a href="{{route('site.terms')}}" class="text-primary pr-3">{{__('site.terms')}}</a>
|
<a href="{{route('site.terms')}}" class="text-primary pr-3">{{__('site.terms')}}</a>
|
||||||
<a href="{{route('site.privacy')}}" class="text-primary pr-3">{{__('site.privacy')}}</a>
|
<a href="{{route('site.privacy')}}" class="text-primary pr-3">{{__('site.privacy')}}</a>
|
||||||
|
<a href="{{route('discover.places')}}" class="text-primary pr-3">Places</a>
|
||||||
<a href="{{route('site.language')}}" class="text-primary pr-3">{{__('site.language')}}</a>
|
<a href="{{route('site.language')}}" class="text-primary pr-3">{{__('site.language')}}</a>
|
||||||
<a href="https://pixelfed.org" class="text-muted float-right" rel="noopener" title="version {{config('pixelfed.version')}}" data-toggle="tooltip">Powered by Pixelfed</a>
|
<a href="https://pixelfed.org" class="text-muted float-right" rel="noopener" title="version {{config('pixelfed.version')}}" data-toggle="tooltip">Powered by Pixelfed</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -1,59 +1,48 @@
|
||||||
<nav class="navbar navbar-expand navbar-light navbar-laravel sticky-top">
|
<nav class="navbar navbar-expand navbar-light navbar-laravel shadow-none border-bottom border sticky-top py-1">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<a class="navbar-brand d-flex align-items-center" href="{{ route('timeline.personal') }}" title="Logo">
|
<a class="navbar-brand d-flex align-items-center" href="{{ route('timeline.personal') }}" title="Logo">
|
||||||
<img src="/img/pixelfed-icon-color.svg" height="30px" class="px-2">
|
<img src="/img/pixelfed-icon-color.svg" height="30px" class="px-2" loading="eager">
|
||||||
<span class="font-weight-bold mb-0 d-none d-sm-block" style="font-size:20px;">{{ config('app.name', 'pixelfed') }}</span>
|
<span class="font-weight-bold mb-0 d-none d-sm-block" style="font-size:20px;">{{ config('app.name', 'pixelfed') }}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
<div class="collapse navbar-collapse">
|
||||||
@auth
|
@auth
|
||||||
<ul class="navbar-nav d-none d-md-block mx-auto pr-3">
|
<ul class="navbar-nav d-none d-md-block mx-auto">
|
||||||
<form class="form-inline search-bar" method="get" action="/i/results">
|
<form class="form-inline search-bar" method="get" action="/i/results">
|
||||||
<div class="input-group">
|
<input class="form-control form-control-sm" name="q" placeholder="{{__('navmenu.search')}}" aria-label="search" autocomplete="off" required style="line-height: 0.6;width:200px">
|
||||||
<input class="form-control" name="q" placeholder="{{__('navmenu.search')}}" aria-label="search" autocomplete="off" required>
|
|
||||||
<div class="input-group-append">
|
|
||||||
<button class="btn btn-outline-primary" type="submit"><i class="fas fa-search"></i></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</ul>
|
</ul>
|
||||||
@endauth
|
@endauth
|
||||||
|
|
||||||
@guest
|
@guest
|
||||||
|
|
||||||
<ul class="navbar-nav ml-auto">
|
<ul class="navbar-nav ml-auto">
|
||||||
<li><a class="nav-link font-weight-bold text-primary" href="{{ route('login') }}" title="Login">{{ __('Login') }}</a></li>
|
<li>
|
||||||
@if(config('pixelfed.open_registration'))
|
<a class="nav-link font-weight-bold text-primary" href="{{ route('login') }}" title="Login">
|
||||||
<li><a class="nav-link font-weight-bold" href="{{ route('register') }}" title="Register">{{ __('Register') }}</a></li>@endif
|
{{ __('Login') }}
|
||||||
@else
|
</a>
|
||||||
<ul class="navbar-nav ml-auto">
|
</li>
|
||||||
|
@if(config('pixelfed.open_registration'))
|
||||||
|
<li>
|
||||||
|
<a class="nav-link font-weight-bold" href="{{ route('register') }}" title="Register">
|
||||||
|
{{ __('Register') }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
@endif
|
||||||
|
@else
|
||||||
|
<div class="ml-auto">
|
||||||
|
<ul class="navbar-nav">
|
||||||
<div class="d-none d-md-block">
|
<div class="d-none d-md-block">
|
||||||
<li class="nav-item px-md-2">
|
<li class="nav-item px-md-2">
|
||||||
<a class="nav-link font-weight-bold {{request()->is('/') ?'text-dark':'text-muted'}}" href="/" title="Home Timeline" data-toggle="tooltip" data-placement="bottom">
|
<a class="nav-link font-weight-bold text-muted" href="/" title="Home Timeline" data-toggle="tooltip" data-placement="bottom">
|
||||||
<i class="fas fa-home fa-lg"></i>
|
<i class="fas fa-home fa-lg"></i>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</div>
|
</div>
|
||||||
{{-- <div class="d-none d-md-block">
|
<li class="d-block d-md-none"></li>
|
||||||
<li class="nav-item px-md-2">
|
|
||||||
<a class="nav-link font-weight-bold {{request()->is('timeline/public') ?'text-primary':''}}" href="/timeline/public" title="Public Timeline" data-toggle="tooltip" data-placement="bottom">
|
|
||||||
<i class="far fa-map fa-lg"></i>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</div> --}}
|
|
||||||
|
|
||||||
<li class="d-block d-md-none">
|
|
||||||
|
|
||||||
</li>
|
|
||||||
|
|
||||||
{{-- <li class="pr-2">
|
|
||||||
<a class="nav-link font-weight-bold {{request()->is('timeline/network') ?'text-primary':''}}" href="{{route('timeline.network')}}" title="Network Timeline">
|
|
||||||
<i class="fas fa-globe fa-lg"></i>
|
|
||||||
</a>
|
|
||||||
</li> --}}
|
|
||||||
<div class="d-none d-md-block">
|
<div class="d-none d-md-block">
|
||||||
<li class="nav-item px-md-2">
|
<li class="nav-item px-md-2">
|
||||||
<a class="nav-link font-weight-bold {{request()->is('*discover*') ?'text-dark':'text-muted'}}" href="{{route('discover')}}" title="Discover" data-toggle="tooltip" data-placement="bottom">
|
<a class="nav-link font-weight-bold text-muted" href="{{route('discover')}}" title="Discover" data-toggle="tooltip" data-placement="bottom">
|
||||||
<i class="far fa-compass fa-lg"></i>
|
<i class="far fa-compass fa-lg"></i>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -62,23 +51,20 @@
|
||||||
<li class="nav-item px-md-2">
|
<li class="nav-item px-md-2">
|
||||||
<div title="Create new post" data-toggle="tooltip" data-placement="bottom">
|
<div title="Create new post" data-toggle="tooltip" data-placement="bottom">
|
||||||
<a href="{{route('compose')}}" class="nav-link" data-toggle="modal" data-target="#composeModal">
|
<a href="{{route('compose')}}" class="nav-link" data-toggle="modal" data-target="#composeModal">
|
||||||
<i class="fas fa-camera-retro fa-lg text-primary"></i>
|
<i class="fas fa-camera-retro fa-lg text-muted"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</div>
|
</div>
|
||||||
</ul>
|
<li class="nav-item dropdown ml-2">
|
||||||
<ul class="navbar-nav">
|
|
||||||
<li class="nav-item dropdown">
|
|
||||||
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" title="User Menu" data-toggle="tooltip" data-placement="bottom">
|
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" title="User Menu" data-toggle="tooltip" data-placement="bottom">
|
||||||
<img class="rounded-circle box-shadow mr-1" src="{{Auth::user()->profile->avatarUrl()}}" width="26px" height="26px">
|
<i class="far fa-user fa-lg text-muted"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
|
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
|
||||||
<a class="dropdown-item font-weight-ultralight text-truncate" href="{{Auth::user()->url()}}">
|
<a class="dropdown-item font-weight-bold" href="/i/me">
|
||||||
<img class="rounded-circle box-shadow mr-1" src="{{Auth::user()->profile->avatarUrl()}}" width="26px" height="26px">
|
<span class="far fa-user pr-1"></span>
|
||||||
@{{Auth::user()->username}}
|
{{__('navmenu.myProfile')}}
|
||||||
<p class="small mb-0 text-muted text-center">{{__('navmenu.viewMyProfile')}}</p>
|
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
<a class="d-block d-md-none dropdown-item font-weight-bold" href="{{route('discover')}}">
|
<a class="d-block d-md-none dropdown-item font-weight-bold" href="{{route('discover')}}">
|
||||||
|
@ -98,18 +84,6 @@
|
||||||
<span class="far fa-map pr-1"></span>
|
<span class="far fa-map pr-1"></span>
|
||||||
{{__('navmenu.publicTimeline')}}
|
{{__('navmenu.publicTimeline')}}
|
||||||
</a>
|
</a>
|
||||||
{{-- <a class="dropdown-item font-weight-bold" href="{{route('timeline.network')}}">
|
|
||||||
<span class="fas fa-globe pr-1"></span>
|
|
||||||
Network Timeline
|
|
||||||
</a> --}}
|
|
||||||
{{-- <a class="dropdown-item font-weight-bold" href="{{route('messages')}}">
|
|
||||||
<span class="far fa-envelope pr-1"></span>
|
|
||||||
{{__('navmenu.directMessages')}}
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item font-weight-bold" href="{{route('account.circles')}}">
|
|
||||||
<span class="far fa-circle pr-1"></span>
|
|
||||||
{{__('Circles')}}
|
|
||||||
</a>--}}
|
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
<a class="dropdown-item font-weight-bold" href="{{route('settings')}}">
|
<a class="dropdown-item font-weight-bold" href="{{route('settings')}}">
|
||||||
<span class="fas fa-cog pr-1"></span>
|
<span class="fas fa-cog pr-1"></span>
|
||||||
|
@ -134,7 +108,8 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@endguest
|
</div>
|
||||||
|
@endguest
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
64
resources/views/settings/invites/create.blade.php
Normal file
64
resources/views/settings/invites/create.blade.php
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
@extends('settings.template')
|
||||||
|
|
||||||
|
@section('section')
|
||||||
|
|
||||||
|
<div class="title">
|
||||||
|
<h3 class="font-weight-bold">Send Invite</h3>
|
||||||
|
<p class="lead">Invite friends or family to join you on <span class="font-weight-bold">{{config('pixelfed.domain.app')}}</span></p>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
@if(config('pixelfed.user_invites.limit.daily') != 0)
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<div class="font-weight-bold">Warning</div>
|
||||||
|
<p class="mb-0">You may only send {{config('pixelfed.user_invites.limit.daily')}} invite(s) per day.</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<form method="post">
|
||||||
|
@csrf
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Email address</label>
|
||||||
|
<input type="email" class="form-control" name="email" placeholder="friend@example.org" autocomplete="off">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Message</label>
|
||||||
|
<textarea class="form-control" name="message" placeholder="Add an optional message" rows="2"></textarea>
|
||||||
|
<p class="help-text mb-0 text-right small text-muted"><span class="message-count">0</span>/<span class="message-limit">500</span></p>
|
||||||
|
</div>
|
||||||
|
<div class="form-group form-check">
|
||||||
|
<input type="checkbox" class="form-check-input" id="tos" name="tos">
|
||||||
|
<label class="form-check-label font-weight-bold small" for="tos">I confirm this invitation is not in violation of the <a href="{{route('site.terms')}}">Terms of Service</a> and <a href="{{route('site.privacy')}}">Privacy Policy</a>.</label>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<p class="float-right">
|
||||||
|
<button type="submit" class="btn btn-primary font-weight-bold py-0 form-submit">Send Invite</button>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
$('textarea[name="message"]').on('change keyup paste', function(e) {
|
||||||
|
let el = $(this);
|
||||||
|
let len = el.val().length;
|
||||||
|
let limit = $('.message-limit').text();
|
||||||
|
|
||||||
|
if(len > 100) {
|
||||||
|
el.attr('rows', '4');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(len > limit) {
|
||||||
|
let diff = len - limit;
|
||||||
|
$('.message-count').addClass('text-danger').text('-'+diff);
|
||||||
|
$('.form-submit').attr('disabled','');
|
||||||
|
} else {
|
||||||
|
$('.message-count').removeClass('text-danger').text(len);
|
||||||
|
$('.form-submit').removeAttr('disabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
@endpush
|
45
resources/views/settings/invites/home.blade.php
Normal file
45
resources/views/settings/invites/home.blade.php
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
@extends('settings.template')
|
||||||
|
|
||||||
|
@section('section')
|
||||||
|
|
||||||
|
<div class="title">
|
||||||
|
<h3 class="font-weight-bold">Invites</h3>
|
||||||
|
<p class="lead">Send email invites to your friends and family!</p>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
@if($invites->count() > 0)
|
||||||
|
<table class="table table-light">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">#</th>
|
||||||
|
<th scope="col">Email</th>
|
||||||
|
<th scope="col">Valid For</th>
|
||||||
|
<th scope="col">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($invites as $invite)
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{{$invite->id}}</th>
|
||||||
|
<td>{{$invite->email}}</td>
|
||||||
|
<td>{{$invite->message}}</td>
|
||||||
|
<td>
|
||||||
|
@if($invite->used_at == null)
|
||||||
|
<button class="btn btn-outline-danger btn-sm">Delete</button>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
@else
|
||||||
|
<div class="d-flex align-items-center justify-content-center text-center pb-4">
|
||||||
|
<div>
|
||||||
|
<p class="pt-5"><i class="far fa-envelope-open text-lighter fa-6x"></i></p>
|
||||||
|
<p class="lead">You haven't invited anyone yet.</p>
|
||||||
|
<p><a class="btn btn-primary btn-lg py-0 font-weight-bold" href="{{route('settings.invites.create')}}">Invite someone</a></p>
|
||||||
|
<p class="font-weight-lighter text-muted">You have <b class="font-weight-bold text-dark">{{$limit - $used}}</b> invites left.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@endsection
|
|
@ -177,6 +177,11 @@
|
||||||
<label class="form-check-label font-weight-bold">Simple Mode (Timelines only)</label>
|
<label class="form-check-label font-weight-bold">Simple Mode (Timelines only)</label>
|
||||||
<p class="text-muted small help-text">An experimental content-first timeline layout</p>
|
<p class="text-muted small help-text">An experimental content-first timeline layout</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-check pb-3">
|
||||||
|
<input class="form-check-input" type="checkbox" id="show_tips">
|
||||||
|
<label class="form-check-label font-weight-bold">Show Tips</label>
|
||||||
|
<p class="text-muted small help-text">Show Tips on Timelines (Desktop Only)</p>
|
||||||
|
</div>
|
||||||
<div class="py-3">
|
<div class="py-3">
|
||||||
<p class="font-weight-bold text-muted text-center">Discovery</p>
|
<p class="font-weight-bold text-muted text-center">Discovery</p>
|
||||||
<hr>
|
<hr>
|
||||||
|
@ -218,6 +223,10 @@ $(document).ready(function() {
|
||||||
$('#distraction_free').attr('checked', true);
|
$('#distraction_free').attr('checked', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(localStorage.getItem('metro-tips') !== 'false') {
|
||||||
|
$('#show_tips').attr('checked', true);
|
||||||
|
}
|
||||||
|
|
||||||
$('#show_suggestions').on('change', function(e) {
|
$('#show_suggestions').on('change', function(e) {
|
||||||
if(e.target.checked) {
|
if(e.target.checked) {
|
||||||
localStorage.removeItem('pf_metro_ui.exp.rec');
|
localStorage.removeItem('pf_metro_ui.exp.rec');
|
||||||
|
@ -241,6 +250,14 @@ $(document).ready(function() {
|
||||||
localStorage.removeItem('pf_metro_ui.exp.df');
|
localStorage.removeItem('pf_metro_ui.exp.df');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#show_tips').on('change', function(e) {
|
||||||
|
if(e.target.checked) {
|
||||||
|
localStorage.setItem('metro-tips', true);
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem('metro-tips');
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@endpush
|
@endpush
|
|
@ -10,6 +10,8 @@
|
||||||
<p>
|
<p>
|
||||||
<a class="btn btn-outline-secondary py-0 font-weight-bold" href="{{route('settings.privacy.muted-users')}}">Muted Users</a>
|
<a class="btn btn-outline-secondary py-0 font-weight-bold" href="{{route('settings.privacy.muted-users')}}">Muted Users</a>
|
||||||
<a class="btn btn-outline-primary py-0 font-weight-bold" href="{{route('settings.privacy.blocked-users')}}">Blocked Users</a>
|
<a class="btn btn-outline-primary py-0 font-weight-bold" href="{{route('settings.privacy.blocked-users')}}">Blocked Users</a>
|
||||||
|
{{-- <a class="btn btn-outline-secondary py-0 font-weight-bold" href="{{route('settings.privacy.blocked-keywords')}}">Blocked keywords</a>
|
||||||
|
<a class="btn btn-outline-secondary py-0 font-weight-bold" href="{{route('settings.privacy.blocked-instances')}}">Blocked instances</a> --}}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@if($users->count() > 0)
|
@if($users->count() > 0)
|
||||||
|
@ -17,7 +19,7 @@
|
||||||
@foreach($users as $user)
|
@foreach($users as $user)
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<div class="d-flex justify-content-between align-items-center font-weight-bold">
|
<div class="d-flex justify-content-between align-items-center font-weight-bold">
|
||||||
<span><img class="rounded-circle mr-3" src="{{$user->avatarUrl()}}" width="32px">{{$user->emailUrl()}}</span>
|
<span><a href="{{$user->url()}}" class="text-decoration-none text-dark"><img class="rounded-circle mr-3" src="{{$user->avatarUrl()}}" width="32px">{{$user->username}}</a></span>
|
||||||
<span class="btn-group">
|
<span class="btn-group">
|
||||||
<form method="post">
|
<form method="post">
|
||||||
@csrf
|
@csrf
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
<p>
|
<p>
|
||||||
<a class="btn btn-outline-primary py-0 font-weight-bold" href="{{route('settings.privacy.muted-users')}}">Muted Users</a>
|
<a class="btn btn-outline-primary py-0 font-weight-bold" href="{{route('settings.privacy.muted-users')}}">Muted Users</a>
|
||||||
<a class="btn btn-outline-secondary py-0 font-weight-bold" href="{{route('settings.privacy.blocked-users')}}">Blocked Users</a>
|
<a class="btn btn-outline-secondary py-0 font-weight-bold" href="{{route('settings.privacy.blocked-users')}}">Blocked Users</a>
|
||||||
|
{{-- <a class="btn btn-outline-secondary py-0 font-weight-bold" href="{{route('settings.privacy.blocked-keywords')}}">Blocked keywords</a>
|
||||||
|
<a class="btn btn-outline-secondary py-0 font-weight-bold" href="{{route('settings.privacy.blocked-instances')}}">Blocked instances</a> --}}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@if($users->count() > 0)
|
@if($users->count() > 0)
|
||||||
|
@ -17,7 +19,7 @@
|
||||||
@foreach($users as $user)
|
@foreach($users as $user)
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<div class="d-flex justify-content-between align-items-center font-weight-bold">
|
<div class="d-flex justify-content-between align-items-center font-weight-bold">
|
||||||
<span><img class="rounded-circle mr-3" src="{{$user->avatarUrl()}}" width="32px">{{$user->emailUrl()}}</span>
|
<span><a href="{{$user->url()}}" class="text-decoration-none text-dark"><img class="rounded-circle mr-3" src="{{$user->avatarUrl()}}" width="32px">{{$user->username}}</a></span>
|
||||||
<span class="btn-group">
|
<span class="btn-group">
|
||||||
<form method="post">
|
<form method="post">
|
||||||
@csrf
|
@csrf
|
||||||
|
|
|
@ -2,31 +2,32 @@
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
|
|
||||||
{{-- <div class="container mt-5">
|
<div class="compose-container container mt-5 d-none">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 col-md-6 offset-md-3">
|
<div class="col-12 col-md-6 offset-md-3">
|
||||||
<p class="lead text-center font-weight-bold">Compose New Post</p>
|
<p class="lead text-center">
|
||||||
<p class="lead text-center">
|
<a href="javascript:void(0)" class="btn btn-primary font-weight-bold" data-toggle="modal" data-target="#composeModal">Compose New Post</a>
|
||||||
<a href="javascript:void(0)" class="btn btn-primary font-weight-bold" data-toggle="modal" data-target="#composeModal">New Post</a>
|
</p>
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div> --}}
|
</div>
|
||||||
|
|
||||||
<div class="modal pr-0" tabindex="-1" role="dialog" id="composeModal">
|
<div class="modal pr-0" tabindex="-1" role="dialog" id="composeModal">
|
||||||
<div class="modal-dialog" role="document">
|
<div class="modal-dialog" role="document">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<compose-classic></compose-classic>
|
<compose-classic></compose-classic>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@push('scripts')
|
@push('scripts')
|
||||||
<script type="text/javascript" src="{{ mix('js/compose-classic.js') }}"></script>
|
<script type="text/javascript" src="{{ mix('js/compose-classic.js') }}"></script>
|
||||||
<script type="text/javascript">App.boot();</script>
|
<script type="text/javascript">
|
||||||
<script type="text/javascript">
|
App.boot();
|
||||||
$('#composeModal').modal('show');
|
$('#composeModal').modal('show');
|
||||||
</script>
|
$('.compose-container').removeClass('d-none');
|
||||||
|
</script>
|
||||||
|
|
||||||
@endpush
|
@endpush
|
|
@ -68,7 +68,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
||||||
Route::get('/home', 'HomeController@index')->name('home');
|
Route::get('/home', 'HomeController@index')->name('home');
|
||||||
|
|
||||||
Route::get('discover/c/{slug}', 'DiscoverController@showCategory');
|
Route::get('discover/c/{slug}', 'DiscoverController@showCategory');
|
||||||
Route::get('discover/personal', 'DiscoverController@showPersonal');
|
Route::redirect('discover/personal', '/discover');
|
||||||
Route::get('discover', 'DiscoverController@home')->name('discover');
|
Route::get('discover', 'DiscoverController@home')->name('discover');
|
||||||
Route::get('discover/loops', 'DiscoverController@showLoops');
|
Route::get('discover/loops', 'DiscoverController@showLoops');
|
||||||
|
|
||||||
|
@ -121,6 +121,9 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
||||||
Route::delete('collection/{id}', 'CollectionController@delete')->middleware('throttle:maxCollectionsPerHour,60')->middleware('throttle:maxCollectionsPerDay,1440')->middleware('throttle:maxCollectionsPerMonth,43800');
|
Route::delete('collection/{id}', 'CollectionController@delete')->middleware('throttle:maxCollectionsPerHour,60')->middleware('throttle:maxCollectionsPerDay,1440')->middleware('throttle:maxCollectionsPerMonth,43800');
|
||||||
Route::post('collection/{id}/publish', 'CollectionController@publish')->middleware('throttle:maxCollectionsPerHour,60')->middleware('throttle:maxCollectionsPerDay,1440')->middleware('throttle:maxCollectionsPerMonth,43800');
|
Route::post('collection/{id}/publish', 'CollectionController@publish')->middleware('throttle:maxCollectionsPerHour,60')->middleware('throttle:maxCollectionsPerDay,1440')->middleware('throttle:maxCollectionsPerMonth,43800');
|
||||||
Route::get('profile/collections/{id}', 'CollectionController@getUserCollections');
|
Route::get('profile/collections/{id}', 'CollectionController@getUserCollections');
|
||||||
|
|
||||||
|
Route::post('compose/media/update/{id}', 'MediaController@composeUpdate')->middleware('throttle:maxComposeMediaUpdatesPerHour,60')->middleware('throttle:maxComposeMediaUpdatesPerDay,1440')->middleware('throttle:maxComposeMediaUpdatesPerMonth,43800');
|
||||||
|
Route::get('compose/location/search', 'ApiController@composeLocationSearch');
|
||||||
});
|
});
|
||||||
Route::group(['prefix' => 'admin'], function () {
|
Route::group(['prefix' => 'admin'], function () {
|
||||||
Route::post('moderate', 'Api\AdminApiController@moderate');
|
Route::post('moderate', 'Api\AdminApiController@moderate');
|
||||||
|
@ -129,6 +132,9 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::get('discover/tags/{hashtag}', 'DiscoverController@showTags');
|
Route::get('discover/tags/{hashtag}', 'DiscoverController@showTags');
|
||||||
|
Route::get('discover/places', 'PlaceController@directoryHome')->name('discover.places');
|
||||||
|
Route::get('discover/places/{id}/{slug}', 'PlaceController@show');
|
||||||
|
Route::get('discover/location/country/{country}', 'PlaceController@directoryCities');
|
||||||
|
|
||||||
Route::group(['prefix' => 'i'], function () {
|
Route::group(['prefix' => 'i'], function () {
|
||||||
Route::redirect('/', '/');
|
Route::redirect('/', '/');
|
||||||
|
@ -333,6 +339,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
||||||
Route::get('p/{username}/{id}/c', 'CommentController@showAll');
|
Route::get('p/{username}/{id}/c', 'CommentController@showAll');
|
||||||
Route::get('p/{username}/{id}/edit', 'StatusController@edit');
|
Route::get('p/{username}/{id}/edit', 'StatusController@edit');
|
||||||
Route::post('p/{username}/{id}/edit', 'StatusController@editStore');
|
Route::post('p/{username}/{id}/edit', 'StatusController@editStore');
|
||||||
|
Route::get('p/{username}/{id}.json', 'StatusController@showObject');
|
||||||
Route::get('p/{username}/{id}', 'StatusController@show');
|
Route::get('p/{username}/{id}', 'StatusController@show');
|
||||||
Route::get('{username}/followers', 'ProfileController@followers')->middleware('auth');
|
Route::get('{username}/followers', 'ProfileController@followers')->middleware('auth');
|
||||||
Route::get('{username}/following', 'ProfileController@following')->middleware('auth');
|
Route::get('{username}/following', 'ProfileController@following')->middleware('auth');
|
||||||
|
|
0
tests/database.sqlite
Normal file
0
tests/database.sqlite
Normal file
Loading…
Reference in a new issue