mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-11-22 14:31:26 +00:00
Merge branch 'dev' of https://github.com/dansup/pixelfed into dev
This commit is contained in:
commit
16dc76db17
81 changed files with 2455 additions and 1023 deletions
|
@ -32,6 +32,8 @@ MAIL_PORT=2525
|
||||||
MAIL_USERNAME=null
|
MAIL_USERNAME=null
|
||||||
MAIL_PASSWORD=null
|
MAIL_PASSWORD=null
|
||||||
MAIL_ENCRYPTION=null
|
MAIL_ENCRYPTION=null
|
||||||
|
MAIL_FROM_ADDRESS="pixelfed@example.com"
|
||||||
|
MAIL_FROM_NAME="Pixelfed"
|
||||||
|
|
||||||
SESSION_DOMAIN="${APP_DOMAIN}"
|
SESSION_DOMAIN="${APP_DOMAIN}"
|
||||||
SESSION_SECURE_COOKIE=true
|
SESSION_SECURE_COOKIE=true
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# PixelFed: Federated Image Sharing
|
# PixelFed: Federated Image Sharing
|
||||||
|
|
||||||
PixelFed is a federated social image sharing platform, similar to instagram.
|
PixelFed is a federated social image sharing platform, similar to Instagram.
|
||||||
Federation is done using the [ActivityPub](https://activitypub.rocks/) protocol,
|
Federation is done using the [ActivityPub](https://activitypub.rocks/) protocol,
|
||||||
which is used by [Mastodon](http://joinmastodon.org/), [PeerTube](https://joinpeertube.org/en/),
|
which is used by [Mastodon](http://joinmastodon.org/), [PeerTube](https://joinpeertube.org/en/),
|
||||||
[Pleroma](https://pleroma.social/), and more. Through ActivityPub PixelFed can share
|
[Pleroma](https://pleroma.social/), and more. Through ActivityPub PixelFed can share
|
||||||
|
|
|
@ -40,6 +40,25 @@ class AccountController extends Controller
|
||||||
return view('account.activity', compact('profile', 'notifications'));
|
return view('account.activity', compact('profile', 'notifications'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function followingActivity(Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'page' => 'nullable|min:1|max:3',
|
||||||
|
'a' => 'nullable|alpha_dash',
|
||||||
|
]);
|
||||||
|
$profile = Auth::user()->profile;
|
||||||
|
$action = $request->input('a');
|
||||||
|
$timeago = Carbon::now()->subMonths(1);
|
||||||
|
$following = $profile->following->pluck('id');
|
||||||
|
$notifications = Notification::whereIn('actor_id', $following)
|
||||||
|
->where('profile_id', '!=', $profile->id)
|
||||||
|
->whereDate('created_at', '>', $timeago)
|
||||||
|
->orderBy('notifications.id','desc')
|
||||||
|
->simplePaginate(30);
|
||||||
|
|
||||||
|
return view('account.following', compact('profile', 'notifications'));
|
||||||
|
}
|
||||||
|
|
||||||
public function verifyEmail(Request $request)
|
public function verifyEmail(Request $request)
|
||||||
{
|
{
|
||||||
return view('account.verify_email');
|
return view('account.verify_email');
|
||||||
|
|
113
app/Http/Controllers/Api/BaseApiController.php
Normal file
113
app/Http/Controllers/Api/BaseApiController.php
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use Auth, Cache;
|
||||||
|
use App\{
|
||||||
|
Avatar,
|
||||||
|
Like,
|
||||||
|
Profile,
|
||||||
|
Status
|
||||||
|
};
|
||||||
|
use League\Fractal;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Controllers\AvatarController;
|
||||||
|
use App\Util\Webfinger\Webfinger;
|
||||||
|
use App\Transformer\Api\{
|
||||||
|
AccountTransformer,
|
||||||
|
StatusTransformer
|
||||||
|
};
|
||||||
|
use App\Jobs\AvatarPipeline\AvatarOptimize;
|
||||||
|
use League\Fractal\Serializer\ArraySerializer;
|
||||||
|
|
||||||
|
class BaseApiController extends Controller
|
||||||
|
{
|
||||||
|
protected $fractal;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware('auth');
|
||||||
|
$this->fractal = new Fractal\Manager();
|
||||||
|
$this->fractal->setSerializer(new ArraySerializer());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function accounts(Request $request, $id)
|
||||||
|
{
|
||||||
|
$profile = Profile::findOrFail($id);
|
||||||
|
$resource = new Fractal\Resource\Item($profile, new AccountTransformer);
|
||||||
|
$res = $this->fractal->createData($resource)->toArray();
|
||||||
|
return response()->json($res, 200, [], JSON_PRETTY_PRINT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function accountFollowers(Request $request, $id)
|
||||||
|
{
|
||||||
|
$profile = Profile::findOrFail($id);
|
||||||
|
$followers = $profile->followers;
|
||||||
|
$resource = new Fractal\Resource\Collection($followers, new AccountTransformer);
|
||||||
|
$res = $this->fractal->createData($resource)->toArray();
|
||||||
|
return response()->json($res, 200, [], JSON_PRETTY_PRINT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function accountFollowing(Request $request, $id)
|
||||||
|
{
|
||||||
|
$profile = Profile::findOrFail($id);
|
||||||
|
$following = $profile->following;
|
||||||
|
$resource = new Fractal\Resource\Collection($following, new AccountTransformer);
|
||||||
|
$res = $this->fractal->createData($resource)->toArray();
|
||||||
|
return response()->json($res, 200, [], JSON_PRETTY_PRINT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function accountStatuses(Request $request, $id)
|
||||||
|
{
|
||||||
|
$profile = Profile::findOrFail($id);
|
||||||
|
$statuses = $profile->statuses()->orderBy('id', 'desc')->paginate(20);
|
||||||
|
$resource = new Fractal\Resource\Collection($statuses, new StatusTransformer);
|
||||||
|
$res = $this->fractal->createData($resource)->toArray();
|
||||||
|
return response()->json($res, 200, [], JSON_PRETTY_PRINT);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function followSuggestions(Request $request)
|
||||||
|
{
|
||||||
|
$followers = Auth::user()->profile->recommendFollowers();
|
||||||
|
$resource = new Fractal\Resource\Collection($followers, new AccountTransformer);
|
||||||
|
$res = $this->fractal->createData($resource)->toArray();
|
||||||
|
return response()->json($res);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function avatarUpdate(Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'upload' => 'required|mimes:jpeg,png,gif|max:2000',
|
||||||
|
]);
|
||||||
|
try {
|
||||||
|
$user = Auth::user();
|
||||||
|
$profile = $user->profile;
|
||||||
|
$file = $request->file('upload');
|
||||||
|
$path = (new AvatarController())->getPath($user, $file);
|
||||||
|
$dir = $path['root'];
|
||||||
|
$name = $path['name'];
|
||||||
|
$public = $path['storage'];
|
||||||
|
$currentAvatar = storage_path('app/'.$profile->avatar->media_path);
|
||||||
|
$loc = $request->file('upload')->storeAs($public, $name);
|
||||||
|
|
||||||
|
$avatar = Avatar::whereProfileId($profile->id)->firstOrFail();
|
||||||
|
$opath = $avatar->media_path;
|
||||||
|
$avatar->media_path = "$public/$name";
|
||||||
|
$avatar->thumb_path = null;
|
||||||
|
$avatar->change_count = ++$avatar->change_count;
|
||||||
|
$avatar->last_processed_at = null;
|
||||||
|
$avatar->save();
|
||||||
|
|
||||||
|
Cache::forget("avatar:{$profile->id}");
|
||||||
|
AvatarOptimize::dispatch($user->profile, $currentAvatar);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'code' => 200,
|
||||||
|
'msg' => 'Avatar successfully updated'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,16 +2,13 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use Auth;
|
use Auth, Cache;
|
||||||
use App\Like;
|
use App\{Like, Status};
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use App\Http\Controllers\Api\BaseApiController;
|
||||||
|
|
||||||
class ApiController extends Controller
|
class ApiController extends BaseApiController
|
||||||
{
|
{
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->middleware('auth');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function hydrateLikes(Request $request)
|
public function hydrateLikes(Request $request)
|
||||||
{
|
{
|
||||||
|
@ -21,12 +18,18 @@ class ApiController extends Controller
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$profile = Auth::user()->profile;
|
$profile = Auth::user()->profile;
|
||||||
|
$res = Cache::remember('api:like-ids:user:'.$profile->id, 1440, function() use ($profile) {
|
||||||
$likes = Like::whereProfileId($profile->id)
|
return Like::whereProfileId($profile->id)
|
||||||
->orderBy('id', 'desc')
|
->orderBy('id', 'desc')
|
||||||
->take(1000)
|
->take(1000)
|
||||||
->pluck('status_id');
|
->pluck('status_id');
|
||||||
|
});
|
||||||
|
|
||||||
return response()->json($likes);
|
return response()->json($res);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadMoreComments(Request $request)
|
||||||
|
{
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,93 @@
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Auth, Cache, Log, Storage;
|
||||||
|
use App\Avatar;
|
||||||
|
use App\Jobs\AvatarPipeline\AvatarOptimize;
|
||||||
|
|
||||||
class AvatarController extends Controller
|
class AvatarController extends Controller
|
||||||
{
|
{
|
||||||
//
|
public function __construct()
|
||||||
|
{
|
||||||
|
return $this->middleware('auth');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'avatar' => 'required|mimes:jpeg,png|max:2000'
|
||||||
|
]);
|
||||||
|
try {
|
||||||
|
$user = Auth::user();
|
||||||
|
$profile = $user->profile;
|
||||||
|
$file = $request->file('avatar');
|
||||||
|
$path = $this->getPath($user, $file);
|
||||||
|
$dir = $path['root'];
|
||||||
|
$name = $path['name'];
|
||||||
|
$public = $path['storage'];
|
||||||
|
$currentAvatar = storage_path('app/'.$profile->avatar->media_path);
|
||||||
|
$loc = $request->file('avatar')->storeAs($public, $name);
|
||||||
|
|
||||||
|
$avatar = Avatar::whereProfileId($profile->id)->firstOrFail();
|
||||||
|
$opath = $avatar->media_path;
|
||||||
|
$avatar->media_path = "$public/$name";
|
||||||
|
$avatar->thumb_path = null;
|
||||||
|
$avatar->change_count = ++$avatar->change_count;
|
||||||
|
$avatar->last_processed_at = null;
|
||||||
|
$avatar->save();
|
||||||
|
|
||||||
|
Cache::forget("avatar:{$profile->id}");
|
||||||
|
AvatarOptimize::dispatch($user->profile, $currentAvatar);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
}
|
||||||
|
return redirect()->back()->with('status', 'Avatar updated successfully. It may take a few minutes to update across the site.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPath($user, $file)
|
||||||
|
{
|
||||||
|
$basePath = storage_path('app/public/avatars');
|
||||||
|
$this->checkDir($basePath);
|
||||||
|
|
||||||
|
$id = $user->profile->id;
|
||||||
|
$path = $this->buildPath($id);
|
||||||
|
$dir = storage_path('app/'.$path);
|
||||||
|
$this->checkDir($dir);
|
||||||
|
$name = 'avatar.' . $file->guessExtension();
|
||||||
|
$res = ['root' => 'storage/app/' . $path, 'name' => $name, 'storage' => $path];
|
||||||
|
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkDir($path)
|
||||||
|
{
|
||||||
|
if(!is_dir($path)) {
|
||||||
|
mkdir($path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildPath($id)
|
||||||
|
{
|
||||||
|
$padded = str_pad($id, 12, 0, STR_PAD_LEFT);
|
||||||
|
$parts = str_split($padded, 3);
|
||||||
|
foreach($parts as $k => $part) {
|
||||||
|
if($k == 0) {
|
||||||
|
$prefix = storage_path('app/public/avatars/'.$parts[0]);
|
||||||
|
$this->checkDir($prefix);
|
||||||
|
}
|
||||||
|
if($k == 1) {
|
||||||
|
$prefix = storage_path('app/public/avatars/'.$parts[0].'/'.$parts[1]);
|
||||||
|
$this->checkDir($prefix);
|
||||||
|
}
|
||||||
|
if($k == 2) {
|
||||||
|
$prefix = storage_path('app/public/avatars/'.$parts[0].'/'.$parts[1].'/'.$parts[2]);
|
||||||
|
$this->checkDir($prefix);
|
||||||
|
}
|
||||||
|
if($k == 3) {
|
||||||
|
$avatarpath = 'public/avatars/'.$parts[0].'/'.$parts[1].'/'.$parts[2].'/'.$parts[3];
|
||||||
|
$prefix = storage_path('app/'.$avatarpath);
|
||||||
|
$this->checkDir($prefix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $avatarpath;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,10 @@ class BookmarkController extends Controller
|
||||||
['status_id' => $status->id], ['profile_id' => $profile->id]
|
['status_id' => $status->id], ['profile_id' => $profile->id]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if(!$bookmark->wasRecentlyCreated) {
|
||||||
|
$bookmark->delete();
|
||||||
|
}
|
||||||
|
|
||||||
if($request->ajax()) {
|
if($request->ajax()) {
|
||||||
$response = ['code' => 200, 'msg' => 'Bookmark saved!'];
|
$response = ['code' => 200, 'msg' => 'Bookmark saved!'];
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -18,6 +18,14 @@ class CommentController extends Controller
|
||||||
return view('status.reply', compact('user', 'status'));
|
return view('status.reply', compact('user', 'status'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function showAll(Request $request, $username, int $id)
|
||||||
|
{
|
||||||
|
$user = Profile::whereUsername($username)->firstOrFail();
|
||||||
|
$status = Status::whereProfileId($user->id)->findOrFail($id);
|
||||||
|
$replies = Status::whereInReplyToId($id)->paginate(40);
|
||||||
|
return view('status.comments', compact('user', 'status', 'replies'));
|
||||||
|
}
|
||||||
|
|
||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
{
|
{
|
||||||
if(Auth::check() === false) { abort(403); }
|
if(Auth::check() === false) { abort(403); }
|
||||||
|
|
|
@ -15,17 +15,44 @@ class DiscoverController extends Controller
|
||||||
|
|
||||||
public function home()
|
public function home()
|
||||||
{
|
{
|
||||||
$following = Follower::whereProfileId(Auth::user()->profile->id)->pluck('following_id');
|
$pid = Auth::user()->profile->id;
|
||||||
$people = Profile::inRandomOrder()->where('id', '!=', Auth::user()->profile->id)->whereNotIn('id', $following)->take(3)->get();
|
|
||||||
$posts = Status::whereHas('media')->where('profile_id', '!=', Auth::user()->profile->id)->whereNotIn('profile_id', $following)->orderBy('created_at', 'desc')->take('21')->get();
|
$following = Follower::whereProfileId($pid)
|
||||||
|
->pluck('following_id');
|
||||||
|
|
||||||
|
$people = Profile::inRandomOrder()
|
||||||
|
->where('id', '!=', $pid)
|
||||||
|
->whereNotIn('id', $following)
|
||||||
|
->take(3)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$posts = Status::whereHas('media')
|
||||||
|
->where('profile_id', '!=', $pid)
|
||||||
|
->whereNotIn('profile_id', $following)
|
||||||
|
->orderBy('created_at', 'desc')
|
||||||
|
->simplePaginate(21);
|
||||||
|
|
||||||
return view('discover.home', compact('people', 'posts'));
|
return view('discover.home', compact('people', 'posts'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function showTags(Request $request, $hashtag)
|
public function showTags(Request $request, $hashtag)
|
||||||
{
|
{
|
||||||
$tag = Hashtag::whereSlug($hashtag)->firstOrFail();
|
$this->validate($request, [
|
||||||
$posts = $tag->posts()->has('media')->orderBy('id','desc')->paginate(12);
|
'page' => 'nullable|integer|min:1|max:10'
|
||||||
$count = $tag->posts()->has('media')->orderBy('id','desc')->count();
|
]);
|
||||||
return view('discover.tags.show', compact('tag', 'posts', 'count'));
|
|
||||||
|
$tag = Hashtag::with('posts')
|
||||||
|
->withCount('posts')
|
||||||
|
->whereSlug($hashtag)
|
||||||
|
->firstOrFail();
|
||||||
|
|
||||||
|
$posts = $tag->posts()
|
||||||
|
->whereIsNsfw(false)
|
||||||
|
->whereVisibility('public')
|
||||||
|
->has('media')
|
||||||
|
->orderBy('id','desc')
|
||||||
|
->simplePaginate(12);
|
||||||
|
|
||||||
|
return view('discover.tags.show', compact('tag', 'posts'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,7 +118,7 @@ class FederationController extends Controller
|
||||||
{
|
{
|
||||||
$this->validate($request, ['resource'=>'required|string|min:3|max:255']);
|
$this->validate($request, ['resource'=>'required|string|min:3|max:255']);
|
||||||
|
|
||||||
$hash = hash('sha512', $request->input('resource'));
|
$hash = hash('sha256', $request->input('resource'));
|
||||||
|
|
||||||
$webfinger = Cache::remember('api:webfinger:'.$hash, 1440, function() use($request) {
|
$webfinger = Cache::remember('api:webfinger:'.$hash, 1440, function() use($request) {
|
||||||
$resource = $request->input('resource');
|
$resource = $request->input('resource');
|
||||||
|
@ -141,7 +141,7 @@ class FederationController extends Controller
|
||||||
$fractal = new Fractal\Manager();
|
$fractal = new Fractal\Manager();
|
||||||
$resource = new Fractal\Resource\Item($user, new ProfileOutbox);
|
$resource = new Fractal\Resource\Item($user, new ProfileOutbox);
|
||||||
$res = $fractal->createData($resource)->toArray();
|
$res = $fractal->createData($resource)->toArray();
|
||||||
return response()->json($res['data']);
|
return response(json_encode($res['data']))->header('Content-Type', 'application/activity+json');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function userInbox(Request $request, $username)
|
public function userInbox(Request $request, $username)
|
||||||
|
|
|
@ -68,7 +68,7 @@ class ProfileController extends Controller
|
||||||
$fractal = new Fractal\Manager();
|
$fractal = new Fractal\Manager();
|
||||||
$resource = new Fractal\Resource\Item($user, new ProfileTransformer);
|
$resource = new Fractal\Resource\Item($user, new ProfileTransformer);
|
||||||
$res = $fractal->createData($resource)->toArray();
|
$res = $fractal->createData($resource)->toArray();
|
||||||
return response()->json($res['data']);
|
return response(json_encode($res['data']))->header('Content-Type', 'application/activity+json');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function showAtomFeed(Request $request, $user)
|
public function showAtomFeed(Request $request, $user)
|
||||||
|
@ -109,11 +109,12 @@ class ProfileController extends Controller
|
||||||
abort(403);
|
abort(403);
|
||||||
}
|
}
|
||||||
$user = Auth::user()->profile;
|
$user = Auth::user()->profile;
|
||||||
|
$settings = User::whereUsername($username)->firstOrFail()->settings;
|
||||||
$owner = true;
|
$owner = true;
|
||||||
$following = false;
|
$following = false;
|
||||||
$timeline = $user->bookmarks()->withCount(['likes','comments'])->orderBy('created_at','desc')->simplePaginate(10);
|
$timeline = $user->bookmarks()->withCount(['likes','comments'])->orderBy('created_at','desc')->simplePaginate(10);
|
||||||
$is_following = ($owner == false && Auth::check()) ? $user->followedBy(Auth::user()->profile) : false;
|
$is_following = ($owner == false && Auth::check()) ? $user->followedBy(Auth::user()->profile) : false;
|
||||||
$is_admin = is_null($user->domain) ? $user->user->is_admin : false;
|
$is_admin = is_null($user->domain) ? $user->user->is_admin : false;
|
||||||
return view('profile.show', compact('user', 'owner', 'following', 'timeline', 'is_following', 'is_admin'));
|
return view('profile.show', compact('user', 'settings', 'owner', 'following', 'timeline', 'is_following', 'is_admin'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,25 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Auth;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use App\{Avatar, Profile, Report, Status, User};
|
||||||
|
|
||||||
class ReportController extends Controller
|
class ReportController extends Controller
|
||||||
{
|
{
|
||||||
|
protected $profile;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware('auth');
|
||||||
|
}
|
||||||
|
|
||||||
public function showForm(Request $request)
|
public function showForm(Request $request)
|
||||||
{
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'type' => 'required|alpha_dash',
|
||||||
|
'id' => 'required|integer|min:1'
|
||||||
|
]);
|
||||||
return view('report.form');
|
return view('report.form');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,4 +48,92 @@ class ReportController extends Controller
|
||||||
{
|
{
|
||||||
return view('report.spam.profile');
|
return view('report.spam.profile');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function sensitiveCommentForm(Request $request)
|
||||||
|
{
|
||||||
|
return view('report.sensitive.comment');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sensitivePostForm(Request $request)
|
||||||
|
{
|
||||||
|
return view('report.sensitive.post');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sensitiveProfileForm(Request $request)
|
||||||
|
{
|
||||||
|
return view('report.sensitive.profile');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function abusiveCommentForm(Request $request)
|
||||||
|
{
|
||||||
|
return view('report.abusive.comment');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function abusivePostForm(Request $request)
|
||||||
|
{
|
||||||
|
return view('report.abusive.post');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function abusiveProfileForm(Request $request)
|
||||||
|
{
|
||||||
|
return view('report.abusive.profile');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function formStore(Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'report' => 'required|alpha_dash',
|
||||||
|
'type' => 'required|alpha_dash',
|
||||||
|
'id' => 'required|integer|min:1',
|
||||||
|
'msg' => 'nullable|string|max:150'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$profile = Auth::user()->profile;
|
||||||
|
$reportType = $request->input('report');
|
||||||
|
$object_id = $request->input('id');
|
||||||
|
$object_type = $request->input('type');
|
||||||
|
$msg = $request->input('msg');
|
||||||
|
$object = null;
|
||||||
|
$types = ['spam', 'sensitive', 'abusive'];
|
||||||
|
|
||||||
|
if(!in_array($reportType, $types)) {
|
||||||
|
return redirect('/timeline')->with('error', 'Invalid report type');
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($object_type) {
|
||||||
|
case 'post':
|
||||||
|
$object = Status::findOrFail($object_id);
|
||||||
|
$object_type = 'App\Status';
|
||||||
|
$exists = Report::whereUserId(Auth::id())
|
||||||
|
->whereObjectId($object->id)
|
||||||
|
->whereObjectType('App\Status')
|
||||||
|
->count();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return redirect('/timeline')->with('error', 'Invalid report type');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($exists !== 0) {
|
||||||
|
return redirect('/timeline')->with('error', 'You have already reported this!');
|
||||||
|
}
|
||||||
|
|
||||||
|
if($object->profile_id == $profile->id) {
|
||||||
|
return redirect('/timeline')->with('error', 'You cannot report your own content!');
|
||||||
|
}
|
||||||
|
|
||||||
|
$report = new Report;
|
||||||
|
$report->profile_id = $profile->id;
|
||||||
|
$report->user_id = Auth::id();
|
||||||
|
$report->object_id = $object->id;
|
||||||
|
$report->object_type = $object_type;
|
||||||
|
$report->reported_profile_id = $object->profile_id;
|
||||||
|
$report->type = $request->input('report');
|
||||||
|
$report->message = $request->input('msg');
|
||||||
|
$report->save();
|
||||||
|
|
||||||
|
return redirect('/timeline')->with('status', 'Report successfully sent!');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,44 +3,74 @@
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use App\{AccountLog, Profile, User};
|
use App\{AccountLog, EmailVerification, Media, Profile, User};
|
||||||
use Auth, DB;
|
use Auth, DB;
|
||||||
|
use App\Util\Lexer\PrettyNumber;
|
||||||
|
|
||||||
class SettingsController extends Controller
|
class SettingsController extends Controller
|
||||||
{
|
{
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
return $this->middleware('auth');
|
$this->middleware('auth');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function home()
|
public function home()
|
||||||
{
|
{
|
||||||
return view('settings.home');
|
$id = Auth::user()->profile->id;
|
||||||
|
$storage = [];
|
||||||
|
$used = Media::whereProfileId($id)->sum('size');
|
||||||
|
$storage['limit'] = config('pixelfed.max_account_size') * 1024;
|
||||||
|
$storage['used'] = $used;
|
||||||
|
$storage['percentUsed'] = ceil($storage['used'] / $storage['limit'] * 100);
|
||||||
|
$storage['limitPretty'] = PrettyNumber::size($storage['limit']);
|
||||||
|
$storage['usedPretty'] = PrettyNumber::size($storage['used']);
|
||||||
|
return view('settings.home', compact('storage'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function homeUpdate(Request $request)
|
public function homeUpdate(Request $request)
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'name' => 'required|string|max:30',
|
'name' => 'required|string|max:30',
|
||||||
'bio' => 'nullable|string|max:125'
|
'bio' => 'nullable|string|max:125',
|
||||||
|
'website' => 'nullable|url',
|
||||||
|
'email' => 'nullable|email'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$changes = false;
|
$changes = false;
|
||||||
$name = $request->input('name');
|
$name = $request->input('name');
|
||||||
$bio = $request->input('bio');
|
$bio = $request->input('bio');
|
||||||
|
$website = $request->input('website');
|
||||||
|
$email = $request->input('email');
|
||||||
$user = Auth::user();
|
$user = Auth::user();
|
||||||
$profile = $user->profile;
|
$profile = $user->profile;
|
||||||
|
|
||||||
|
|
||||||
|
if($user->email != $email) {
|
||||||
|
$changes = true;
|
||||||
|
$user->email = $email;
|
||||||
|
$user->email_verified_at = null;
|
||||||
|
// Prevent old verifications from working
|
||||||
|
EmailVerification::whereUserId($user->id)->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only allow email to be updated if not yet verified
|
||||||
|
if(!$changes && $user->email_verified_at) {
|
||||||
if($profile->name != $name) {
|
if($profile->name != $name) {
|
||||||
$changes = true;
|
$changes = true;
|
||||||
$user->name = $name;
|
$user->name = $name;
|
||||||
$profile->name = $name;
|
$profile->name = $name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if($profile->website != $website) {
|
||||||
|
$changes = true;
|
||||||
|
$profile->website = $website;
|
||||||
|
}
|
||||||
|
|
||||||
if($profile->bio != $bio) {
|
if($profile->bio != $bio) {
|
||||||
$changes = true;
|
$changes = true;
|
||||||
$profile->bio = $bio;
|
$profile->bio = $bio;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if($changes === true) {
|
if($changes === true) {
|
||||||
$user->save();
|
$user->save();
|
||||||
|
|
|
@ -2,9 +2,10 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App, Auth;
|
use App, Auth, Cache;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use App\{Follower, Status, User};
|
use App\{Follower, Profile, Status, User};
|
||||||
|
use App\Util\Lexer\PrettyNumber;
|
||||||
|
|
||||||
class SiteController extends Controller
|
class SiteController extends Controller
|
||||||
{
|
{
|
||||||
|
@ -20,7 +21,7 @@ class SiteController extends Controller
|
||||||
|
|
||||||
public function homeGuest()
|
public function homeGuest()
|
||||||
{
|
{
|
||||||
return view('site.index');
|
return view('welcome');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function homeTimeline()
|
public function homeTimeline()
|
||||||
|
@ -29,10 +30,12 @@ class SiteController extends Controller
|
||||||
$following = Follower::whereProfileId(Auth::user()->profile->id)->pluck('following_id');
|
$following = Follower::whereProfileId(Auth::user()->profile->id)->pluck('following_id');
|
||||||
$following->push(Auth::user()->profile->id);
|
$following->push(Auth::user()->profile->id);
|
||||||
$timeline = Status::whereIn('profile_id', $following)
|
$timeline = Status::whereIn('profile_id', $following)
|
||||||
|
->whereHas('media')
|
||||||
->orderBy('id','desc')
|
->orderBy('id','desc')
|
||||||
->withCount(['comments', 'likes', 'shares'])
|
->withCount(['comments', 'likes', 'shares'])
|
||||||
->simplePaginate(10);
|
->simplePaginate(20);
|
||||||
return view('timeline.template', compact('timeline'));
|
$type = 'personal';
|
||||||
|
return view('timeline.template', compact('timeline', 'type'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function changeLocale(Request $request, $locale)
|
public function changeLocale(Request $request, $locale)
|
||||||
|
@ -43,4 +46,20 @@ class SiteController extends Controller
|
||||||
App::setLocale($locale);
|
App::setLocale($locale);
|
||||||
return redirect()->back();
|
return redirect()->back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function about()
|
||||||
|
{
|
||||||
|
$res = Cache::remember('site:page:about', 15, function() {
|
||||||
|
$statuses = Status::whereHas('media')
|
||||||
|
->whereNull('in_reply_to_id')
|
||||||
|
->whereNull('reblog_of_id')
|
||||||
|
->count();
|
||||||
|
$statusCount = PrettyNumber::convert($statuses);
|
||||||
|
$userCount = PrettyNumber::convert(User::count());
|
||||||
|
$remoteCount = PrettyNumber::convert(Profile::whereNotNull('remote_url')->count());
|
||||||
|
$adminContact = User::whereIsAdmin(true)->first();
|
||||||
|
return view('site.about')->with(compact('statusCount', 'userCount', 'remoteCount', 'adminContact'))->render();
|
||||||
|
});
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,17 @@ class StatusController extends Controller
|
||||||
if(!$status->media_path && $status->in_reply_to_id) {
|
if(!$status->media_path && $status->in_reply_to_id) {
|
||||||
return redirect($status->url());
|
return redirect($status->url());
|
||||||
}
|
}
|
||||||
return view('status.show', compact('user', 'status'));
|
$replies = Status::whereInReplyToId($status->id)->simplePaginate(30);
|
||||||
|
return view('status.show', compact('user', 'status', 'replies'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function compose()
|
||||||
|
{
|
||||||
|
if(Auth::check() == false)
|
||||||
|
{
|
||||||
|
abort(403);
|
||||||
|
}
|
||||||
|
return view('status.compose');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
|
@ -32,6 +42,12 @@ class StatusController extends Controller
|
||||||
|
|
||||||
$user = Auth::user();
|
$user = Auth::user();
|
||||||
|
|
||||||
|
$size = Media::whereUserId($user->id)->sum('size') / 1000;
|
||||||
|
$limit = (int) config('pixelfed.max_account_size');
|
||||||
|
if($size >= $limit) {
|
||||||
|
return redirect()->back()->with('error', 'You have exceeded your storage limit. Please click <a href="#">here</a> for more info.');
|
||||||
|
}
|
||||||
|
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'photo.*' => 'required|mimes:jpeg,png,bmp,gif|max:' . config('pixelfed.max_photo_size'),
|
'photo.*' => 'required|mimes:jpeg,png,bmp,gif|max:' . config('pixelfed.max_photo_size'),
|
||||||
'caption' => 'string|max:' . config('pixelfed.max_caption_length'),
|
'caption' => 'string|max:' . config('pixelfed.max_caption_length'),
|
||||||
|
@ -43,7 +59,6 @@ class StatusController extends Controller
|
||||||
if(count($request->file('photo')) > config('pixelfed.max_album_length')) {
|
if(count($request->file('photo')) > config('pixelfed.max_album_length')) {
|
||||||
return redirect()->back()->with('error', 'Too many files, max limit per post: ' . config('pixelfed.max_album_length'));
|
return redirect()->back()->with('error', 'Too many files, max limit per post: ' . config('pixelfed.max_album_length'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$cw = $request->filled('cw') && $request->cw == 'on' ? true : false;
|
$cw = $request->filled('cw') && $request->cw == 'on' ? true : false;
|
||||||
$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);
|
||||||
|
@ -102,4 +117,43 @@ class StatusController extends Controller
|
||||||
|
|
||||||
return redirect(Auth::user()->url());
|
return redirect(Auth::user()->url());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function storeShare(Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'item' => 'required|integer',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$profile = Auth::user()->profile;
|
||||||
|
$status = Status::withCount('shares')->findOrFail($request->input('item'));
|
||||||
|
|
||||||
|
$count = $status->shares_count;
|
||||||
|
|
||||||
|
$exists = Status::whereProfileId(Auth::user()->profile->id)
|
||||||
|
->whereReblogOfId($status->id)
|
||||||
|
->count();
|
||||||
|
if($exists !== 0) {
|
||||||
|
$shares = Status::whereProfileId(Auth::user()->profile->id)
|
||||||
|
->whereReblogOfId($status->id)
|
||||||
|
->get();
|
||||||
|
foreach($shares as $share) {
|
||||||
|
$share->delete();
|
||||||
|
$count--;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$share = new Status;
|
||||||
|
$share->profile_id = $profile->id;
|
||||||
|
$share->reblog_of_id = $status->id;
|
||||||
|
$share->save();
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($request->ajax()) {
|
||||||
|
$response = ['code' => 200, 'msg' => 'Share saved', 'count' => $count];
|
||||||
|
} else {
|
||||||
|
$response = redirect($status->url());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,24 +18,23 @@ class TimelineController extends Controller
|
||||||
// TODO: Use redis for timelines
|
// TODO: Use redis for timelines
|
||||||
$following = Follower::whereProfileId(Auth::user()->profile->id)->pluck('following_id');
|
$following = Follower::whereProfileId(Auth::user()->profile->id)->pluck('following_id');
|
||||||
$following->push(Auth::user()->profile->id);
|
$following->push(Auth::user()->profile->id);
|
||||||
$timeline = Status::whereHas('media')
|
$timeline = Status::whereIn('profile_id', $following)
|
||||||
->whereNull('in_reply_to_id')
|
|
||||||
->whereIn('profile_id', $following)
|
|
||||||
->orderBy('id','desc')
|
->orderBy('id','desc')
|
||||||
->withCount(['comments', 'likes'])
|
->simplePaginate(20);
|
||||||
->simplePaginate(10);
|
$type = 'personal';
|
||||||
return view('timeline.personal', compact('timeline'));
|
return view('timeline.template', compact('timeline', 'type'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function local()
|
public function local()
|
||||||
{
|
{
|
||||||
// TODO: Use redis for timelines
|
// TODO: Use redis for timelines
|
||||||
|
// $timeline = Timeline::build()->local();
|
||||||
$timeline = Status::whereHas('media')
|
$timeline = Status::whereHas('media')
|
||||||
->whereNull('in_reply_to_id')
|
->whereNull('in_reply_to_id')
|
||||||
->orderBy('id','desc')
|
->orderBy('id','desc')
|
||||||
->withCount(['comments', 'likes'])
|
->simplePaginate(20);
|
||||||
->simplePaginate(10);
|
$type = 'local';
|
||||||
return view('timeline.public', compact('timeline'));
|
return view('timeline.template', compact('timeline', 'type'));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,7 @@ 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') && !$request->is('log*') &&
|
!$request->is('i/verify-email', 'log*', 'i/confirm-email/*', 'settings/home')
|
||||||
!$request->is('i/confirm-email/*')
|
|
||||||
) {
|
) {
|
||||||
return redirect('/i/verify-email');
|
return redirect('/i/verify-email');
|
||||||
}
|
}
|
||||||
|
|
70
app/Jobs/AvatarPipeline/AvatarOptimize.php
Normal file
70
app/Jobs/AvatarPipeline/AvatarOptimize.php
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\AvatarPipeline;
|
||||||
|
|
||||||
|
use \Carbon\Carbon;
|
||||||
|
use Image as Intervention;
|
||||||
|
use App\{Avatar, Profile};
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
|
||||||
|
class AvatarOptimize implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected $profile;
|
||||||
|
protected $current;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(Profile $profile, $current)
|
||||||
|
{
|
||||||
|
$this->profile = $profile;
|
||||||
|
$this->current = $current;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$avatar = $this->profile->avatar;
|
||||||
|
$file = storage_path("app/$avatar->media_path");
|
||||||
|
|
||||||
|
try {
|
||||||
|
$img = Intervention::make($file)->orientate();
|
||||||
|
$img->fit(200, 200, function ($constraint) {
|
||||||
|
$constraint->upsize();
|
||||||
|
});
|
||||||
|
$quality = config('pixelfed.image_quality');
|
||||||
|
$img->save($file, $quality);
|
||||||
|
|
||||||
|
$avatar = Avatar::whereProfileId($this->profile->id)->firstOrFail();
|
||||||
|
$avatar->thumb_path = $avatar->media_path;
|
||||||
|
$avatar->change_count = ++$avatar->change_count;
|
||||||
|
$avatar->last_processed_at = Carbon::now();
|
||||||
|
$avatar->save();
|
||||||
|
$this->deleteOldAvatar($avatar->media_path, $this->current);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function deleteOldAvatar($new, $current)
|
||||||
|
{
|
||||||
|
if(storage_path('app/' . $new) == $current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(is_file($current)) {
|
||||||
|
@unlink($current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -113,9 +113,9 @@ class StatusEntityLexer implements ShouldQueue
|
||||||
$m->status_id = $status->id;
|
$m->status_id = $status->id;
|
||||||
$m->profile_id = $mentioned->id;
|
$m->profile_id = $mentioned->id;
|
||||||
$m->save();
|
$m->save();
|
||||||
});
|
|
||||||
|
|
||||||
MentionPipeline::dispatch($status, $m);
|
MentionPipeline::dispatch($status, $m);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace App;
|
namespace App;
|
||||||
|
|
||||||
use Storage;
|
use Auth, Cache, Storage;
|
||||||
use App\Util\Lexer\PrettyNumber;
|
use App\Util\Lexer\PrettyNumber;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
@ -130,7 +130,12 @@ class Profile extends Model
|
||||||
|
|
||||||
public function avatarUrl()
|
public function avatarUrl()
|
||||||
{
|
{
|
||||||
$url = url(Storage::url($this->avatar->media_path ?? 'public/avatars/default.png'));
|
$url = Cache::remember("avatar:{$this->id}", 1440, function() {
|
||||||
|
$path = $this->avatar->media_path ?? 'public/avatars/default.png';
|
||||||
|
$version = hash('sha1', $this->avatar->created_at);
|
||||||
|
$path = "{$path}?v={$version}";
|
||||||
|
return url(Storage::url($path));
|
||||||
|
});
|
||||||
return $url;
|
return $url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,4 +143,35 @@ class Profile extends Model
|
||||||
{
|
{
|
||||||
return $this->statuses()->whereHas('media')->count();
|
return $this->statuses()->whereHas('media')->count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function recommendFollowers()
|
||||||
|
{
|
||||||
|
$follows = $this->following()->pluck('followers.id');
|
||||||
|
$following = $this->following()
|
||||||
|
->orderByRaw('rand()')
|
||||||
|
->take(3)
|
||||||
|
->pluck('following_id');
|
||||||
|
$following->push(Auth::id());
|
||||||
|
$following = Follower::whereNotIn('profile_id', $follows)
|
||||||
|
->whereNotIn('following_id', $following)
|
||||||
|
->whereNotIn('following_id', $follows)
|
||||||
|
->whereIn('profile_id', $following)
|
||||||
|
->orderByRaw('rand()')
|
||||||
|
->limit(3)
|
||||||
|
->pluck('following_id');
|
||||||
|
$recommended = [];
|
||||||
|
foreach($following as $follow) {
|
||||||
|
$recommended[] = Profile::findOrFail($follow);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $recommended;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function keyId()
|
||||||
|
{
|
||||||
|
if($this->remote_url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return $this->permalink('#main-key');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
38
app/Report.php
Normal file
38
app/Report.php
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Report extends Model
|
||||||
|
{
|
||||||
|
public function url()
|
||||||
|
{
|
||||||
|
return url('/i/admin/reports/show/' . $this->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reporter()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Profile::class, 'profile_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reported()
|
||||||
|
{
|
||||||
|
$class = $this->object_type;
|
||||||
|
switch ($class) {
|
||||||
|
case 'App\Status':
|
||||||
|
$column = 'id';
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$column = 'id';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return (new $class())->where($column, $this->object_id)->firstOrFail();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reportedUser()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Profile::class, 'reported_profile_id', 'id');
|
||||||
|
}
|
||||||
|
}
|
|
@ -52,6 +52,14 @@ class Status extends Model
|
||||||
return url($path);
|
return url($path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function permalink($suffix = '/activity')
|
||||||
|
{
|
||||||
|
$id = $this->id;
|
||||||
|
$username = $this->profile->username;
|
||||||
|
$path = config('app.url') . "/p/{$username}/{$id}{$suffix}";
|
||||||
|
return url($path);
|
||||||
|
}
|
||||||
|
|
||||||
public function editUrl()
|
public function editUrl()
|
||||||
{
|
{
|
||||||
return $this->url() . '/edit';
|
return $this->url() . '/edit';
|
||||||
|
@ -84,6 +92,9 @@ class Status extends Model
|
||||||
|
|
||||||
public function bookmarked()
|
public function bookmarked()
|
||||||
{
|
{
|
||||||
|
if(!Auth::check()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
$profile = Auth::user()->profile;
|
$profile = Auth::user()->profile;
|
||||||
return Bookmark::whereProfileId($profile->id)->whereStatusId($this->id)->count();
|
return Bookmark::whereProfileId($profile->id)->whereStatusId($this->id)->count();
|
||||||
}
|
}
|
||||||
|
@ -174,4 +185,9 @@ class Status extends Model
|
||||||
return "<a href='{$actorUrl}' class='profile-link'>{$actorName}</a> " .
|
return "<a href='{$actorUrl}' class='profile-link'>{$actorName}</a> " .
|
||||||
__('notification.commented');
|
__('notification.commented');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function recentComments()
|
||||||
|
{
|
||||||
|
return $this->comments()->orderBy('created_at','desc')->take(3);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,6 +118,7 @@ class RestrictedNames {
|
||||||
|
|
||||||
// Static Assets
|
// Static Assets
|
||||||
"assets",
|
"assets",
|
||||||
|
"storage",
|
||||||
|
|
||||||
// Laravel Horizon
|
// Laravel Horizon
|
||||||
"horizon",
|
"horizon",
|
||||||
|
@ -127,18 +128,30 @@ class RestrictedNames {
|
||||||
"api",
|
"api",
|
||||||
"auth",
|
"auth",
|
||||||
"i",
|
"i",
|
||||||
|
"dashboard",
|
||||||
"discover",
|
"discover",
|
||||||
|
"docs",
|
||||||
"home",
|
"home",
|
||||||
"login",
|
"login",
|
||||||
"logout",
|
"logout",
|
||||||
|
"media",
|
||||||
"p",
|
"p",
|
||||||
"password",
|
"password",
|
||||||
|
"reports",
|
||||||
"search",
|
"search",
|
||||||
"settings",
|
"settings",
|
||||||
|
"statuses",
|
||||||
"site",
|
"site",
|
||||||
"timeline",
|
"timeline",
|
||||||
"user",
|
"user",
|
||||||
"users",
|
"users",
|
||||||
|
"400",
|
||||||
|
"401",
|
||||||
|
"403",
|
||||||
|
"404",
|
||||||
|
"500",
|
||||||
|
"503",
|
||||||
|
"504",
|
||||||
];
|
];
|
||||||
|
|
||||||
public static function get()
|
public static function get()
|
||||||
|
|
|
@ -116,7 +116,8 @@ class Image {
|
||||||
$converted = $this->setBaseName($path, $thumbnail, $img->extension);
|
$converted = $this->setBaseName($path, $thumbnail, $img->extension);
|
||||||
$newPath = storage_path('app/'.$converted['path']);
|
$newPath = storage_path('app/'.$converted['path']);
|
||||||
|
|
||||||
$img->save($newPath, 75);
|
$quality = config('pixelfed.image_quality');
|
||||||
|
$img->save($newPath, $quality);
|
||||||
|
|
||||||
if(!$thumbnail) {
|
if(!$thumbnail) {
|
||||||
$media->orientation = $orientation;
|
$media->orientation = $orientation;
|
||||||
|
|
|
@ -31,13 +31,9 @@ class Webfinger {
|
||||||
|
|
||||||
public function generateAliases()
|
public function generateAliases()
|
||||||
{
|
{
|
||||||
$host = parse_url(config('app.url'), PHP_URL_HOST);
|
|
||||||
$username = $this->user->username;
|
|
||||||
$url = $this->user->url();
|
|
||||||
|
|
||||||
$this->aliases = [
|
$this->aliases = [
|
||||||
'acct:'.$username.'@'.$host,
|
$this->user->url(),
|
||||||
$url
|
$this->user->permalink()
|
||||||
];
|
];
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
@ -55,24 +51,12 @@ class Webfinger {
|
||||||
[
|
[
|
||||||
'rel' => 'http://schemas.google.com/g/2010#updates-from',
|
'rel' => 'http://schemas.google.com/g/2010#updates-from',
|
||||||
'type' => 'application/atom+xml',
|
'type' => 'application/atom+xml',
|
||||||
'href' => url("/users/{$user->username}.atom")
|
'href' => $user->permalink('.atom')
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'rel' => 'self',
|
'rel' => 'self',
|
||||||
'type' => 'application/activity+json',
|
'type' => 'application/activity+json',
|
||||||
'href' => $user->permalink()
|
'href' => $user->permalink()
|
||||||
],
|
|
||||||
[
|
|
||||||
'rel' => 'magic-public-key',
|
|
||||||
'href' => null//$user->public_key
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'rel' => 'salmon',
|
|
||||||
'href' => $user->permalink('/salmon')
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'rel' => 'http://ostatus.org/schema/1.0/subscribe',
|
|
||||||
'href' => url('/main/ostatussub?profile={uri}')
|
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
return $this;
|
return $this;
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
"fideloper/proxy": "^4.0",
|
"fideloper/proxy": "^4.0",
|
||||||
"greggilbert/recaptcha": "dev-master",
|
"greggilbert/recaptcha": "dev-master",
|
||||||
"intervention/image": "^2.4",
|
"intervention/image": "^2.4",
|
||||||
"kitetail/zttp": "^0.3.0",
|
"pixelfed/zttp": "^0.4",
|
||||||
"laravel/framework": "5.6.*",
|
"laravel/framework": "5.6.*",
|
||||||
"laravel/horizon": "^1.2",
|
"laravel/horizon": "^1.2",
|
||||||
"laravel/passport": "^6.0",
|
"laravel/passport": "^6.0",
|
||||||
|
|
821
composer.lock
generated
821
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -76,7 +76,7 @@ return [
|
||||||
'connection' => 'redis',
|
'connection' => 'redis',
|
||||||
'queue' => ['default'],
|
'queue' => ['default'],
|
||||||
'balance' => 'simple',
|
'balance' => 'simple',
|
||||||
'processes' => 10,
|
'processes' => 20,
|
||||||
'tries' => 3,
|
'tries' => 3,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
|
@ -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.1.2',
|
'version' => '0.1.5',
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
@ -86,7 +86,7 @@ return [
|
||||||
|
|
|
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
'max_account_size' => env('MAX_ACCOUNT_SIZE', 100000),
|
'max_account_size' => env('MAX_ACCOUNT_SIZE', 1000000),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
@ -106,7 +106,7 @@ return [
|
||||||
| Change the caption length limit for new local posts.
|
| Change the caption length limit for new local posts.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
'max_caption_length' => env('MAX_CAPTION_LENGTH', 150),
|
'max_caption_length' => env('MAX_CAPTION_LENGTH', 500),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
@ -128,4 +128,14 @@ return [
|
||||||
*/
|
*/
|
||||||
'enforce_email_verification' => env('ENFORCE_EMAIL_VERIFICATION', true),
|
'enforce_email_verification' => env('ENFORCE_EMAIL_VERIFICATION', true),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Image Quality
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Set the image optimization quality, must be a value between 1-100.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'image_quality' => (int) env('IMAGE_QUALITY', 80),
|
||||||
|
|
||||||
];
|
];
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
class UpdateStatusTableChangeCaptionToText extends Migration
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
DB::getDoctrineSchemaManager()
|
||||||
|
->getDatabasePlatform()
|
||||||
|
->registerDoctrineTypeMapping('enum', 'string');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('statuses', function ($table) {
|
||||||
|
$table->text('caption')->change();
|
||||||
|
$table->text('rendered')->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
|
@ -53,7 +53,7 @@ services:
|
||||||
- "db-data:/var/lib/mysql"
|
- "db-data:/var/lib/mysql"
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
image: redis:alpine
|
image: redis:4-alpine
|
||||||
volumes:
|
volumes:
|
||||||
- "redis-data:/data"
|
- "redis-data:/data"
|
||||||
networks:
|
networks:
|
||||||
|
|
BIN
public/css/app.css
vendored
BIN
public/css/app.css
vendored
Binary file not shown.
BIN
public/img/favicon.png
Normal file
BIN
public/img/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
BIN
public/img/fred1.gif
Normal file
BIN
public/img/fred1.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
BIN
public/js/app.js
vendored
BIN
public/js/app.js
vendored
Binary file not shown.
BIN
public/js/timeline.js
vendored
BIN
public/js/timeline.js
vendored
Binary file not shown.
Binary file not shown.
46
resources/assets/js/bootstrap.js
vendored
46
resources/assets/js/bootstrap.js
vendored
|
@ -1,13 +1,6 @@
|
||||||
|
|
||||||
window._ = require('lodash');
|
window._ = require('lodash');
|
||||||
window.Popper = require('popper.js').default;
|
window.Popper = require('popper.js').default;
|
||||||
|
import swal from 'sweetalert';
|
||||||
/**
|
|
||||||
* We'll load jQuery and the Bootstrap jQuery plugin which provides support
|
|
||||||
* for JavaScript based Bootstrap features such as modals and tabs. This
|
|
||||||
* code may be modified to fit the specific needs of your application.
|
|
||||||
*/
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
window.pixelfed = {};
|
window.pixelfed = {};
|
||||||
window.$ = window.jQuery = require('jquery');
|
window.$ = window.jQuery = require('jquery');
|
||||||
|
@ -16,6 +9,7 @@ try {
|
||||||
window.filesize = require('filesize');
|
window.filesize = require('filesize');
|
||||||
window.typeahead = require('./lib/typeahead');
|
window.typeahead = require('./lib/typeahead');
|
||||||
window.Bloodhound = require('./lib/bloodhound');
|
window.Bloodhound = require('./lib/bloodhound');
|
||||||
|
window.Vue = require('vue');
|
||||||
|
|
||||||
require('./components/localstorage');
|
require('./components/localstorage');
|
||||||
require('./components/likebutton');
|
require('./components/likebutton');
|
||||||
|
@ -23,45 +17,21 @@ try {
|
||||||
require('./components/searchform');
|
require('./components/searchform');
|
||||||
require('./components/bookmarkform');
|
require('./components/bookmarkform');
|
||||||
require('./components/statusform');
|
require('./components/statusform');
|
||||||
|
|
||||||
|
Vue.component(
|
||||||
|
'follow-suggestions',
|
||||||
|
require('./components/FollowSuggestions.vue')
|
||||||
|
);
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
/**
|
$('[data-toggle="tooltip"]').tooltip();
|
||||||
* We'll load the axios HTTP library which allows us to easily issue requests
|
|
||||||
* to our Laravel back-end. This library automatically handles sending the
|
|
||||||
* CSRF token as a header based on the value of the "XSRF" token cookie.
|
|
||||||
*/
|
|
||||||
|
|
||||||
window.axios = require('axios');
|
window.axios = require('axios');
|
||||||
|
|
||||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||||
|
|
||||||
/**
|
|
||||||
* Next we will register the CSRF Token as a common header with Axios so that
|
|
||||||
* all outgoing HTTP requests automatically have it attached. This is just
|
|
||||||
* a simple convenience so we don't have to attach every token manually.
|
|
||||||
*/
|
|
||||||
|
|
||||||
let token = document.head.querySelector('meta[name="csrf-token"]');
|
let token = document.head.querySelector('meta[name="csrf-token"]');
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
|
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
|
||||||
} else {
|
} else {
|
||||||
console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
|
console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Echo exposes an expressive API for subscribing to channels and listening
|
|
||||||
* for events that are broadcast by Laravel. Echo and event broadcasting
|
|
||||||
* allows your team to easily build robust real-time web applications.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// import Echo from 'laravel-echo'
|
|
||||||
|
|
||||||
// window.Pusher = require('pusher-js');
|
|
||||||
|
|
||||||
// window.Echo = new Echo({
|
|
||||||
// broadcaster: 'pusher',
|
|
||||||
// key: process.env.MIX_PUSHER_APP_KEY,
|
|
||||||
// cluster: process.env.MIX_PUSHER_APP_CLUSTER,
|
|
||||||
// encrypted: true
|
|
||||||
// });
|
|
||||||
|
|
50
resources/assets/js/components/FollowSuggestions.vue
Normal file
50
resources/assets/js/components/FollowSuggestions.vue
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header bg-white">
|
||||||
|
<span class="font-weight-bold h5">Who to follow</span>
|
||||||
|
<span class="small float-right font-weight-bold">
|
||||||
|
<a href="javascript:void(0);" class="pr-2" v-on:click="fetchData">Refresh</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div v-if="results.length == 0">
|
||||||
|
<p class="mb-0 font-weight-bold">You are not following anyone yet, try the <a href="/discover">discover</a> feature to find users to follow.</p>
|
||||||
|
</div>
|
||||||
|
<div v-for="(user, index) in results">
|
||||||
|
<div class="media " style="width:100%">
|
||||||
|
<img class="mr-3" :src="user.avatar" width="40px">
|
||||||
|
<div class="media-body" style="width:70%">
|
||||||
|
<p class="my-0 font-weight-bold text-truncate" style="text-overflow: hidden">{{user.acct}} <span class="text-muted font-weight-normal">@{{user.username}}</span></p>
|
||||||
|
<a class="btn btn-outline-primary px-3 py-0" :href="user.url" style="border-radius:20px;">Follow</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="index != results.length - 1"><hr></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
results: {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.fetchData();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchData() {
|
||||||
|
axios.get('/api/local/i/follow-suggestions')
|
||||||
|
.then(response => {
|
||||||
|
this.results = response.data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
12
resources/assets/js/components/commentform.js
vendored
12
resources/assets/js/components/commentform.js
vendored
|
@ -1,6 +1,12 @@
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
|
||||||
$('.status-comment-focus').on('click', function(el) {
|
$('.status-card > .card-footer').each(function() {
|
||||||
|
$(this).addClass('d-none');
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '.status-comment-focus', function(el) {
|
||||||
|
var form = $(this).parents().eq(2).find('.card-footer');
|
||||||
|
form.removeClass('d-none');
|
||||||
var el = $(this).parents().eq(2).find('input[name="comment"]');
|
var el = $(this).parents().eq(2).find('input[name="comment"]');
|
||||||
el.focus();
|
el.focus();
|
||||||
});
|
});
|
||||||
|
@ -31,7 +37,7 @@ $(document).ready(function() {
|
||||||
|
|
||||||
var comment = '<p class="mb-0"><span class="font-weight-bold pr-1"><bdi><a class="text-dark" href="' + profile + '">' + username + '</a></bdi></span><span class="comment-text">'+ reply + '</span><span class="float-right"><a href="' + permalink + '" class="text-dark small font-weight-bold">1s</a></span></p>';
|
var comment = '<p class="mb-0"><span class="font-weight-bold pr-1"><bdi><a class="text-dark" href="' + profile + '">' + username + '</a></bdi></span><span class="comment-text">'+ reply + '</span><span class="float-right"><a href="' + permalink + '" class="text-dark small font-weight-bold">1s</a></span></p>';
|
||||||
|
|
||||||
comments.prepend(comment);
|
comments.append(comment);
|
||||||
|
|
||||||
commentform.val('');
|
commentform.val('');
|
||||||
commentform.blur();
|
commentform.blur();
|
||||||
|
@ -41,7 +47,5 @@ $(document).ready(function() {
|
||||||
.catch(function (res) {
|
.catch(function (res) {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
16
resources/assets/js/components/likebutton.js
vendored
16
resources/assets/js/components/likebutton.js
vendored
|
@ -1,10 +1,9 @@
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
|
||||||
if(!ls.get('likes')) {
|
pixelfed.fetchLikes = () => {
|
||||||
axios.get('/api/v1/likes')
|
axios.get('/api/v1/likes')
|
||||||
.then(function (res) {
|
.then(function (res) {
|
||||||
ls.set('likes', res.data);
|
ls.set('likes', res.data);
|
||||||
console.log(res);
|
|
||||||
})
|
})
|
||||||
.catch(function (res) {
|
.catch(function (res) {
|
||||||
ls.set('likes', []);
|
ls.set('likes', []);
|
||||||
|
@ -12,7 +11,7 @@ $(document).ready(function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pixelfed.hydrateLikes = function() {
|
pixelfed.hydrateLikes = () => {
|
||||||
var likes = ls.get('likes');
|
var likes = ls.get('likes');
|
||||||
$('.like-form').each(function(i, el) {
|
$('.like-form').each(function(i, el) {
|
||||||
var el = $(el);
|
var el = $(el);
|
||||||
|
@ -20,11 +19,14 @@ $(document).ready(function() {
|
||||||
var heart = el.find('.status-heart');
|
var heart = el.find('.status-heart');
|
||||||
|
|
||||||
if(likes.indexOf(id) != -1) {
|
if(likes.indexOf(id) != -1) {
|
||||||
heart.removeClass('far fa-heart').addClass('fas fa-heart');
|
heart.removeClass('text-dark').addClass('text-primary');
|
||||||
|
} else {
|
||||||
|
heart.removeClass('text-primary').addClass('text-dark');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pixelfed.fetchLikes();
|
||||||
pixelfed.hydrateLikes();
|
pixelfed.hydrateLikes();
|
||||||
|
|
||||||
$(document).on('submit', '.like-form', function(e) {
|
$(document).on('submit', '.like-form', function(e) {
|
||||||
|
@ -33,6 +35,8 @@ $(document).ready(function() {
|
||||||
var id = el.data('id');
|
var id = el.data('id');
|
||||||
axios.post('/i/like', {item: id})
|
axios.post('/i/like', {item: id})
|
||||||
.then(function (res) {
|
.then(function (res) {
|
||||||
|
pixelfed.fetchLikes();
|
||||||
|
pixelfed.hydrateLikes();
|
||||||
var likes = ls.get('likes');
|
var likes = ls.get('likes');
|
||||||
var action = false;
|
var action = false;
|
||||||
var counter = el.parents().eq(1).find('.like-count');
|
var counter = el.parents().eq(1).find('.like-count');
|
||||||
|
@ -40,14 +44,14 @@ $(document).ready(function() {
|
||||||
var heart = el.find('.status-heart');
|
var heart = el.find('.status-heart');
|
||||||
|
|
||||||
if(likes.indexOf(id) > -1) {
|
if(likes.indexOf(id) > -1) {
|
||||||
heart.removeClass('fas fa-heart').addClass('far fa-heart');
|
heart.removeClass('text-primary').addClass('text-dark');
|
||||||
likes = likes.filter(function(item) {
|
likes = likes.filter(function(item) {
|
||||||
return item !== id
|
return item !== id
|
||||||
});
|
});
|
||||||
counter.text(count);
|
counter.text(count);
|
||||||
action = 'unlike';
|
action = 'unlike';
|
||||||
} else {
|
} else {
|
||||||
heart.removeClass('far fa-heart').addClass('fas fa-heart');
|
heart.removeClass('text-dark').addClass('text-primary');
|
||||||
likes.push(id);
|
likes.push(id);
|
||||||
counter.text(count);
|
counter.text(count);
|
||||||
action = 'like';
|
action = 'like';
|
||||||
|
|
30
resources/assets/js/components/statusform.js
vendored
30
resources/assets/js/components/statusform.js
vendored
|
@ -1,9 +1,5 @@
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
|
||||||
$('#statusForm .btn-filter-select').on('click', function(e) {
|
|
||||||
let el = $(this);
|
|
||||||
});
|
|
||||||
|
|
||||||
pixelfed.create = {};
|
pixelfed.create = {};
|
||||||
pixelfed.filters = {};
|
pixelfed.filters = {};
|
||||||
pixelfed.create.hasGeneratedSelect = false;
|
pixelfed.create.hasGeneratedSelect = false;
|
||||||
|
@ -78,7 +74,7 @@ $(document).ready(function() {
|
||||||
pixelfed.create.hasGeneratedSelect = true;
|
pixelfed.create.hasGeneratedSelect = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#fileInput').on('change', function() {
|
$(document).on('change', '#fileInput', function() {
|
||||||
previewImage(this);
|
previewImage(this);
|
||||||
$('#statusForm .form-filters.d-none').removeClass('d-none');
|
$('#statusForm .form-filters.d-none').removeClass('d-none');
|
||||||
$('#statusForm .form-preview.d-none').removeClass('d-none');
|
$('#statusForm .form-preview.d-none').removeClass('d-none');
|
||||||
|
@ -88,23 +84,43 @@ $(document).ready(function() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#filterSelectDropdown').on('change', function() {
|
$(document).on('change', '#filterSelectDropdown', function() {
|
||||||
let el = $(this);
|
let el = $(this);
|
||||||
let filter = el.val();
|
let filter = el.val();
|
||||||
let oldFilter = pixelfed.create.currentFilterClass;
|
let oldFilter = pixelfed.create.currentFilterClass;
|
||||||
if(filter == 'none') {
|
if(filter == 'none') {
|
||||||
|
$('input[name=filter_class]').val('');
|
||||||
|
$('input[name=filter_name]').val('');
|
||||||
$('.filterContainer').removeClass(oldFilter);
|
$('.filterContainer').removeClass(oldFilter);
|
||||||
pixelfed.create.currentFilterClass = false;
|
pixelfed.create.currentFilterClass = false;
|
||||||
pixelfed.create.currentFilterName = 'None';
|
pixelfed.create.currentFilterName = 'None';
|
||||||
$('.form-group.form-preview .form-text').text('Current Filter: No filter selected');
|
$('.form-group.form-preview .form-text').text('Current Filter: No filter selected');
|
||||||
return;
|
return;
|
||||||
}
|
} else {
|
||||||
$('.filterContainer').removeClass(oldFilter).addClass(filter);
|
$('.filterContainer').removeClass(oldFilter).addClass(filter);
|
||||||
pixelfed.create.currentFilterClass = filter;
|
pixelfed.create.currentFilterClass = filter;
|
||||||
pixelfed.create.currentFilterName = el.find(':selected').text();
|
pixelfed.create.currentFilterName = el.find(':selected').text();
|
||||||
$('.form-group.form-preview .form-text').text('Current Filter: ' + pixelfed.create.currentFilterName);
|
$('.form-group.form-preview .form-text').text('Current Filter: ' + pixelfed.create.currentFilterName);
|
||||||
$('input[name=filter_class]').val(pixelfed.create.currentFilterClass);
|
$('input[name=filter_class]').val(pixelfed.create.currentFilterClass);
|
||||||
$('input[name=filter_name]').val(pixelfed.create.currentFilterName);
|
$('input[name=filter_name]').val(pixelfed.create.currentFilterName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(document).on('keyup keydown', '#statusForm textarea[name=caption]', function() {
|
||||||
|
const el = $(this);
|
||||||
|
const len = el.val().length;
|
||||||
|
const limit = el.data('limit');
|
||||||
|
if(len > limit) {
|
||||||
|
const diff = limit - len;
|
||||||
|
$('#statusForm .caption-counter').text(diff).addClass('text-danger');
|
||||||
|
} else {
|
||||||
|
$('#statusForm .caption-counter').text(len).removeClass('text-danger');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('focus', '#statusForm textarea[name=caption]', function() {
|
||||||
|
const el = $(this);
|
||||||
|
el.attr('rows', '3');
|
||||||
|
});
|
||||||
});
|
});
|
62
resources/assets/js/lib/bloodhound.js
vendored
Normal file → Executable file
62
resources/assets/js/lib/bloodhound.js
vendored
Normal file → Executable file
|
@ -1,18 +1,18 @@
|
||||||
/*!
|
/*!
|
||||||
* typeahead.js 0.11.1
|
* typeahead.js 1.2.0
|
||||||
* https://github.com/twitter/typeahead.js
|
* https://github.com/twitter/typeahead.js
|
||||||
* Copyright 2013-2015 Twitter, Inc. and other contributors; Licensed MIT
|
* Copyright 2013-2017 Twitter, Inc. and other contributors; Licensed MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function(root, factory) {
|
(function(root, factory) {
|
||||||
if (typeof define === "function" && define.amd) {
|
if (typeof define === "function" && define.amd) {
|
||||||
define("bloodhound", [ "jquery" ], function(a0) {
|
define([ "jquery" ], function(a0) {
|
||||||
return root["Bloodhound"] = factory(a0);
|
return root["Bloodhound"] = factory(a0);
|
||||||
});
|
});
|
||||||
} else if (typeof exports === "object") {
|
} else if (typeof exports === "object") {
|
||||||
module.exports = factory(require("jquery"));
|
module.exports = factory(require("jquery"));
|
||||||
} else {
|
} else {
|
||||||
root["Bloodhound"] = factory(jQuery);
|
root["Bloodhound"] = factory(root["jQuery"]);
|
||||||
}
|
}
|
||||||
})(this, function($) {
|
})(this, function($) {
|
||||||
var _ = function() {
|
var _ = function() {
|
||||||
|
@ -148,18 +148,27 @@
|
||||||
stringify: function(val) {
|
stringify: function(val) {
|
||||||
return _.isString(val) ? val : JSON.stringify(val);
|
return _.isString(val) ? val : JSON.stringify(val);
|
||||||
},
|
},
|
||||||
|
guid: function() {
|
||||||
|
function _p8(s) {
|
||||||
|
var p = (Math.random().toString(16) + "000000000").substr(2, 8);
|
||||||
|
return s ? "-" + p.substr(0, 4) + "-" + p.substr(4, 4) : p;
|
||||||
|
}
|
||||||
|
return "tt-" + _p8() + _p8(true) + _p8(true) + _p8();
|
||||||
|
},
|
||||||
noop: function() {}
|
noop: function() {}
|
||||||
};
|
};
|
||||||
}();
|
}();
|
||||||
var VERSION = "0.11.1";
|
var VERSION = "1.2.0";
|
||||||
var tokenizers = function() {
|
var tokenizers = function() {
|
||||||
"use strict";
|
"use strict";
|
||||||
return {
|
return {
|
||||||
nonword: nonword,
|
nonword: nonword,
|
||||||
whitespace: whitespace,
|
whitespace: whitespace,
|
||||||
|
ngram: ngram,
|
||||||
obj: {
|
obj: {
|
||||||
nonword: getObjTokenizer(nonword),
|
nonword: getObjTokenizer(nonword),
|
||||||
whitespace: getObjTokenizer(whitespace)
|
whitespace: getObjTokenizer(whitespace),
|
||||||
|
ngram: getObjTokenizer(ngram)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
function whitespace(str) {
|
function whitespace(str) {
|
||||||
|
@ -170,6 +179,19 @@
|
||||||
str = _.toStr(str);
|
str = _.toStr(str);
|
||||||
return str ? str.split(/\W+/) : [];
|
return str ? str.split(/\W+/) : [];
|
||||||
}
|
}
|
||||||
|
function ngram(str) {
|
||||||
|
str = _.toStr(str);
|
||||||
|
var tokens = [], word = "";
|
||||||
|
_.each(str.split(""), function(char) {
|
||||||
|
if (char.match(/\s+/)) {
|
||||||
|
word = "";
|
||||||
|
} else {
|
||||||
|
tokens.push(word + char);
|
||||||
|
word += char;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
function getObjTokenizer(tokenizer) {
|
function getObjTokenizer(tokenizer) {
|
||||||
return function setKey(keys) {
|
return function setKey(keys) {
|
||||||
keys = _.isArray(keys) ? keys : [].slice.call(arguments, 0);
|
keys = _.isArray(keys) ? keys : [].slice.call(arguments, 0);
|
||||||
|
@ -341,9 +363,10 @@
|
||||||
}();
|
}();
|
||||||
var Transport = function() {
|
var Transport = function() {
|
||||||
"use strict";
|
"use strict";
|
||||||
var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests = 6, sharedCache = new LruCache(10);
|
var pendingRequestsCount = 0, pendingRequests = {}, sharedCache = new LruCache(10);
|
||||||
function Transport(o) {
|
function Transport(o) {
|
||||||
o = o || {};
|
o = o || {};
|
||||||
|
this.maxPendingRequests = o.maxPendingRequests || 6;
|
||||||
this.cancelled = false;
|
this.cancelled = false;
|
||||||
this.lastReq = null;
|
this.lastReq = null;
|
||||||
this._send = o.transport;
|
this._send = o.transport;
|
||||||
|
@ -351,7 +374,7 @@
|
||||||
this._cache = o.cache === false ? new LruCache(0) : sharedCache;
|
this._cache = o.cache === false ? new LruCache(0) : sharedCache;
|
||||||
}
|
}
|
||||||
Transport.setMaxPendingRequests = function setMaxPendingRequests(num) {
|
Transport.setMaxPendingRequests = function setMaxPendingRequests(num) {
|
||||||
maxPendingRequests = num;
|
this.maxPendingRequests = num;
|
||||||
};
|
};
|
||||||
Transport.resetCache = function resetCache() {
|
Transport.resetCache = function resetCache() {
|
||||||
sharedCache.reset();
|
sharedCache.reset();
|
||||||
|
@ -369,7 +392,7 @@
|
||||||
}
|
}
|
||||||
if (jqXhr = pendingRequests[fingerprint]) {
|
if (jqXhr = pendingRequests[fingerprint]) {
|
||||||
jqXhr.done(done).fail(fail);
|
jqXhr.done(done).fail(fail);
|
||||||
} else if (pendingRequestsCount < maxPendingRequests) {
|
} else if (pendingRequestsCount < this.maxPendingRequests) {
|
||||||
pendingRequestsCount++;
|
pendingRequestsCount++;
|
||||||
pendingRequests[fingerprint] = this._send(o).done(done).fail(fail).always(always);
|
pendingRequests[fingerprint] = this._send(o).done(done).fail(fail).always(always);
|
||||||
} else {
|
} else {
|
||||||
|
@ -423,6 +446,7 @@
|
||||||
this.identify = o.identify || _.stringify;
|
this.identify = o.identify || _.stringify;
|
||||||
this.datumTokenizer = o.datumTokenizer;
|
this.datumTokenizer = o.datumTokenizer;
|
||||||
this.queryTokenizer = o.queryTokenizer;
|
this.queryTokenizer = o.queryTokenizer;
|
||||||
|
this.matchAnyQueryToken = o.matchAnyQueryToken;
|
||||||
this.reset();
|
this.reset();
|
||||||
}
|
}
|
||||||
_.mixin(SearchIndex.prototype, {
|
_.mixin(SearchIndex.prototype, {
|
||||||
|
@ -459,7 +483,7 @@
|
||||||
tokens = normalizeTokens(this.queryTokenizer(query));
|
tokens = normalizeTokens(this.queryTokenizer(query));
|
||||||
_.each(tokens, function(token) {
|
_.each(tokens, function(token) {
|
||||||
var node, chars, ch, ids;
|
var node, chars, ch, ids;
|
||||||
if (matches && matches.length === 0) {
|
if (matches && matches.length === 0 && !that.matchAnyQueryToken) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
node = that.trie;
|
node = that.trie;
|
||||||
|
@ -471,9 +495,11 @@
|
||||||
ids = node[IDS].slice(0);
|
ids = node[IDS].slice(0);
|
||||||
matches = matches ? getIntersection(matches, ids) : ids;
|
matches = matches ? getIntersection(matches, ids) : ids;
|
||||||
} else {
|
} else {
|
||||||
|
if (!that.matchAnyQueryToken) {
|
||||||
matches = [];
|
matches = [];
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return matches ? _.map(unique(matches), function(id) {
|
return matches ? _.map(unique(matches), function(id) {
|
||||||
return that.datums[id];
|
return that.datums[id];
|
||||||
|
@ -614,10 +640,12 @@
|
||||||
this.url = o.url;
|
this.url = o.url;
|
||||||
this.prepare = o.prepare;
|
this.prepare = o.prepare;
|
||||||
this.transform = o.transform;
|
this.transform = o.transform;
|
||||||
|
this.indexResponse = o.indexResponse;
|
||||||
this.transport = new Transport({
|
this.transport = new Transport({
|
||||||
cache: o.cache,
|
cache: o.cache,
|
||||||
limiter: o.limiter,
|
limiter: o.limiter,
|
||||||
transport: o.transport
|
transport: o.transport,
|
||||||
|
maxPendingRequests: o.maxPendingRequests
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
_.mixin(Remote.prototype, {
|
_.mixin(Remote.prototype, {
|
||||||
|
@ -655,7 +683,9 @@
|
||||||
identify: _.stringify,
|
identify: _.stringify,
|
||||||
datumTokenizer: null,
|
datumTokenizer: null,
|
||||||
queryTokenizer: null,
|
queryTokenizer: null,
|
||||||
|
matchAnyQueryToken: false,
|
||||||
sufficient: 5,
|
sufficient: 5,
|
||||||
|
indexRemote: false,
|
||||||
sorter: null,
|
sorter: null,
|
||||||
local: [],
|
local: [],
|
||||||
prefetch: null,
|
prefetch: null,
|
||||||
|
@ -744,7 +774,7 @@
|
||||||
} else if (o.wildcard) {
|
} else if (o.wildcard) {
|
||||||
prepare = prepareByWildcard;
|
prepare = prepareByWildcard;
|
||||||
} else {
|
} else {
|
||||||
prepare = idenityPrepare;
|
prepare = identityPrepare;
|
||||||
}
|
}
|
||||||
return prepare;
|
return prepare;
|
||||||
function prepareByReplace(query, settings) {
|
function prepareByReplace(query, settings) {
|
||||||
|
@ -755,7 +785,7 @@
|
||||||
settings.url = settings.url.replace(wildcard, encodeURIComponent(query));
|
settings.url = settings.url.replace(wildcard, encodeURIComponent(query));
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
function idenityPrepare(query, settings) {
|
function identityPrepare(query, settings) {
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -806,6 +836,7 @@
|
||||||
this.sorter = o.sorter;
|
this.sorter = o.sorter;
|
||||||
this.identify = o.identify;
|
this.identify = o.identify;
|
||||||
this.sufficient = o.sufficient;
|
this.sufficient = o.sufficient;
|
||||||
|
this.indexRemote = o.indexRemote;
|
||||||
this.local = o.local;
|
this.local = o.local;
|
||||||
this.remote = o.remote ? new Remote(o.remote) : null;
|
this.remote = o.remote ? new Remote(o.remote) : null;
|
||||||
this.prefetch = o.prefetch ? new Prefetch(o.prefetch) : null;
|
this.prefetch = o.prefetch ? new Prefetch(o.prefetch) : null;
|
||||||
|
@ -875,6 +906,8 @@
|
||||||
},
|
},
|
||||||
search: function search(query, sync, async) {
|
search: function search(query, sync, async) {
|
||||||
var that = this, local;
|
var that = this, local;
|
||||||
|
sync = sync || _.noop;
|
||||||
|
async = async || _.noop;
|
||||||
local = this.sorter(this.index.search(query));
|
local = this.sorter(this.index.search(query));
|
||||||
sync(this.remote ? local.slice() : local);
|
sync(this.remote ? local.slice() : local);
|
||||||
if (this.remote && local.length < this.sufficient) {
|
if (this.remote && local.length < this.sufficient) {
|
||||||
|
@ -890,7 +923,8 @@
|
||||||
return that.identify(r) === that.identify(l);
|
return that.identify(r) === that.identify(l);
|
||||||
}) && nonDuplicates.push(r);
|
}) && nonDuplicates.push(r);
|
||||||
});
|
});
|
||||||
async && async(nonDuplicates);
|
that.indexRemote && that.add(nonDuplicates);
|
||||||
|
async(nonDuplicates);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
all: function all() {
|
all: function all() {
|
||||||
|
|
228
resources/assets/js/lib/typeahead.js
vendored
Normal file → Executable file
228
resources/assets/js/lib/typeahead.js
vendored
Normal file → Executable file
|
@ -1,18 +1,18 @@
|
||||||
/*!
|
/*!
|
||||||
* typeahead.js 0.11.1
|
* typeahead.js 1.2.0
|
||||||
* https://github.com/twitter/typeahead.js
|
* https://github.com/twitter/typeahead.js
|
||||||
* Copyright 2013-2015 Twitter, Inc. and other contributors; Licensed MIT
|
* Copyright 2013-2017 Twitter, Inc. and other contributors; Licensed MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function(root, factory) {
|
(function(root, factory) {
|
||||||
if (typeof define === "function" && define.amd) {
|
if (typeof define === "function" && define.amd) {
|
||||||
define("typeahead.js", [ "jquery" ], function(a0) {
|
define([ "jquery" ], function(a0) {
|
||||||
return factory(a0);
|
return factory(a0);
|
||||||
});
|
});
|
||||||
} else if (typeof exports === "object") {
|
} else if (typeof exports === "object") {
|
||||||
module.exports = factory(require("jquery"));
|
module.exports = factory(require("jquery"));
|
||||||
} else {
|
} else {
|
||||||
factory(jQuery);
|
factory(root["jQuery"]);
|
||||||
}
|
}
|
||||||
})(this, function($) {
|
})(this, function($) {
|
||||||
var _ = function() {
|
var _ = function() {
|
||||||
|
@ -148,6 +148,13 @@
|
||||||
stringify: function(val) {
|
stringify: function(val) {
|
||||||
return _.isString(val) ? val : JSON.stringify(val);
|
return _.isString(val) ? val : JSON.stringify(val);
|
||||||
},
|
},
|
||||||
|
guid: function() {
|
||||||
|
function _p8(s) {
|
||||||
|
var p = (Math.random().toString(16) + "000000000").substr(2, 8);
|
||||||
|
return s ? "-" + p.substr(0, 4) + "-" + p.substr(4, 4) : p;
|
||||||
|
}
|
||||||
|
return "tt-" + _p8() + _p8(true) + _p8(true) + _p8();
|
||||||
|
},
|
||||||
noop: function() {}
|
noop: function() {}
|
||||||
};
|
};
|
||||||
}();
|
}();
|
||||||
|
@ -189,7 +196,7 @@
|
||||||
function buildHtml(c) {
|
function buildHtml(c) {
|
||||||
return {
|
return {
|
||||||
wrapper: '<span class="' + c.wrapper + '"></span>',
|
wrapper: '<span class="' + c.wrapper + '"></span>',
|
||||||
menu: '<div class="' + c.menu + '"></div>'
|
menu: '<div role="listbox" class="' + c.menu + '"></div>'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function buildSelectors(classes) {
|
function buildSelectors(classes) {
|
||||||
|
@ -264,10 +271,8 @@
|
||||||
}
|
}
|
||||||
_.mixin(EventBus.prototype, {
|
_.mixin(EventBus.prototype, {
|
||||||
_trigger: function(type, args) {
|
_trigger: function(type, args) {
|
||||||
var $e;
|
var $e = $.Event(namespace + type);
|
||||||
$e = $.Event(namespace + type);
|
this.$el.trigger.call(this.$el, $e, args || []);
|
||||||
(args = args || []).unshift($e);
|
|
||||||
this.$el.trigger.apply(this.$el, args);
|
|
||||||
return $e;
|
return $e;
|
||||||
},
|
},
|
||||||
before: function(type) {
|
before: function(type) {
|
||||||
|
@ -384,7 +389,36 @@
|
||||||
tagName: "strong",
|
tagName: "strong",
|
||||||
className: null,
|
className: null,
|
||||||
wordsOnly: false,
|
wordsOnly: false,
|
||||||
caseSensitive: false
|
caseSensitive: false,
|
||||||
|
diacriticInsensitive: false
|
||||||
|
};
|
||||||
|
var accented = {
|
||||||
|
A: "[AaªÀ-Åà-åĀ-ąǍǎȀ-ȃȦȧᴬᵃḀḁẚẠ-ảₐ℀℁℻⒜Ⓐⓐ㍱-㍴㎀-㎄㎈㎉㎩-㎯㏂㏊㏟㏿Aa]",
|
||||||
|
B: "[BbᴮᵇḂ-ḇℬ⒝Ⓑⓑ㍴㎅-㎇㏃㏈㏔㏝Bb]",
|
||||||
|
C: "[CcÇçĆ-čᶜ℀ℂ℃℅℆ℭⅭⅽ⒞Ⓒⓒ㍶㎈㎉㎝㎠㎤㏄-㏇Cc]",
|
||||||
|
D: "[DdĎďDŽ-džDZ-dzᴰᵈḊ-ḓⅅⅆⅮⅾ⒟Ⓓⓓ㋏㍲㍷-㍹㎗㎭-㎯㏅㏈Dd]",
|
||||||
|
E: "[EeÈ-Ëè-ëĒ-ěȄ-ȇȨȩᴱᵉḘ-ḛẸ-ẽₑ℡ℯℰⅇ⒠Ⓔⓔ㉐㋍㋎Ee]",
|
||||||
|
F: "[FfᶠḞḟ℉ℱ℻⒡Ⓕⓕ㎊-㎌㎙ff-fflFf]",
|
||||||
|
G: "[GgĜ-ģǦǧǴǵᴳᵍḠḡℊ⒢Ⓖⓖ㋌㋍㎇㎍-㎏㎓㎬㏆㏉㏒㏿Gg]",
|
||||||
|
H: "[HhĤĥȞȟʰᴴḢ-ḫẖℋ-ℎ⒣Ⓗⓗ㋌㍱㎐-㎔㏊㏋㏗Hh]",
|
||||||
|
I: "[IiÌ-Ïì-ïĨ-İIJijǏǐȈ-ȋᴵᵢḬḭỈ-ịⁱℐℑℹⅈⅠ-ⅣⅥ-ⅨⅪⅫⅰ-ⅳⅵ-ⅸⅺⅻ⒤Ⓘⓘ㍺㏌㏕fiffiIi]",
|
||||||
|
J: "[JjIJ-ĵLJ-njǰʲᴶⅉ⒥ⒿⓙⱼJj]",
|
||||||
|
K: "[KkĶķǨǩᴷᵏḰ-ḵK⒦Ⓚⓚ㎄㎅㎉㎏㎑㎘㎞㎢㎦㎪㎸㎾㏀㏆㏍-㏏Kk]",
|
||||||
|
L: "[LlĹ-ŀLJ-ljˡᴸḶḷḺ-ḽℒℓ℡Ⅼⅼ⒧Ⓛⓛ㋏㎈㎉㏐-㏓㏕㏖㏿flfflLl]",
|
||||||
|
M: "[MmᴹᵐḾ-ṃ℠™ℳⅯⅿ⒨Ⓜⓜ㍷-㍹㎃㎆㎎㎒㎖㎙-㎨㎫㎳㎷㎹㎽㎿㏁㏂㏎㏐㏔-㏖㏘㏙㏞㏟Mm]",
|
||||||
|
N: "[NnÑñŃ-ʼnNJ-njǸǹᴺṄ-ṋⁿℕ№⒩Ⓝⓝ㎁㎋㎚㎱㎵㎻㏌㏑Nn]",
|
||||||
|
O: "[OoºÒ-Öò-öŌ-őƠơǑǒǪǫȌ-ȏȮȯᴼᵒỌ-ỏₒ℅№ℴ⒪Ⓞⓞ㍵㏇㏒㏖Oo]",
|
||||||
|
P: "[PpᴾᵖṔ-ṗℙ⒫Ⓟⓟ㉐㍱㍶㎀㎊㎩-㎬㎰㎴㎺㏋㏗-㏚Pp]",
|
||||||
|
Q: "[Qqℚ⒬Ⓠⓠ㏃Qq]",
|
||||||
|
R: "[RrŔ-řȐ-ȓʳᴿᵣṘ-ṛṞṟ₨ℛ-ℝ⒭Ⓡⓡ㋍㍴㎭-㎯㏚㏛Rr]",
|
||||||
|
S: "[SsŚ-šſȘșˢṠ-ṣ₨℁℠⒮Ⓢⓢ㎧㎨㎮-㎳㏛㏜stSs]",
|
||||||
|
T: "[TtŢ-ťȚțᵀᵗṪ-ṱẗ℡™⒯Ⓣⓣ㉐㋏㎔㏏ſtstTt]",
|
||||||
|
U: "[UuÙ-Üù-üŨ-ųƯưǓǔȔ-ȗᵁᵘᵤṲ-ṷỤ-ủ℆⒰Ⓤⓤ㍳㍺Uu]",
|
||||||
|
V: "[VvᵛᵥṼ-ṿⅣ-Ⅷⅳ-ⅷ⒱Ⓥⓥⱽ㋎㍵㎴-㎹㏜㏞Vv]",
|
||||||
|
W: "[WwŴŵʷᵂẀ-ẉẘ⒲Ⓦⓦ㎺-㎿㏝Ww]",
|
||||||
|
X: "[XxˣẊ-ẍₓ℻Ⅸ-Ⅻⅸ-ⅻ⒳Ⓧⓧ㏓Xx]",
|
||||||
|
Y: "[YyÝýÿŶ-ŸȲȳʸẎẏẙỲ-ỹ⒴Ⓨⓨ㏉Yy]",
|
||||||
|
Z: "[ZzŹ-žDZ-dzᶻẐ-ẕℤℨ⒵Ⓩⓩ㎐-㎔Zz]"
|
||||||
};
|
};
|
||||||
return function hightlight(o) {
|
return function hightlight(o) {
|
||||||
var regex;
|
var regex;
|
||||||
|
@ -393,7 +427,7 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
o.pattern = _.isArray(o.pattern) ? o.pattern : [ o.pattern ];
|
o.pattern = _.isArray(o.pattern) ? o.pattern : [ o.pattern ];
|
||||||
regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly);
|
regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly, o.diacriticInsensitive);
|
||||||
traverse(o.node, hightlightTextNode);
|
traverse(o.node, hightlightTextNode);
|
||||||
function hightlightTextNode(textNode) {
|
function hightlightTextNode(textNode) {
|
||||||
var match, patternNode, wrapperNode;
|
var match, patternNode, wrapperNode;
|
||||||
|
@ -419,10 +453,17 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
function getRegex(patterns, caseSensitive, wordsOnly) {
|
function accent_replacer(chr) {
|
||||||
|
return accented[chr.toUpperCase()] || chr;
|
||||||
|
}
|
||||||
|
function getRegex(patterns, caseSensitive, wordsOnly, diacriticInsensitive) {
|
||||||
var escapedPatterns = [], regexStr;
|
var escapedPatterns = [], regexStr;
|
||||||
for (var i = 0, len = patterns.length; i < len; i++) {
|
for (var i = 0, len = patterns.length; i < len; i++) {
|
||||||
escapedPatterns.push(_.escapeRegExChars(patterns[i]));
|
var escapedWord = _.escapeRegExChars(patterns[i]);
|
||||||
|
if (diacriticInsensitive) {
|
||||||
|
escapedWord = escapedWord.replace(/\S/g, accent_replacer);
|
||||||
|
}
|
||||||
|
escapedPatterns.push(escapedWord);
|
||||||
}
|
}
|
||||||
regexStr = wordsOnly ? "\\b(" + escapedPatterns.join("|") + ")\\b" : "(" + escapedPatterns.join("|") + ")";
|
regexStr = wordsOnly ? "\\b(" + escapedPatterns.join("|") + ")\\b" : "(" + escapedPatterns.join("|") + ")";
|
||||||
return caseSensitive ? new RegExp(regexStr) : new RegExp(regexStr, "i");
|
return caseSensitive ? new RegExp(regexStr) : new RegExp(regexStr, "i");
|
||||||
|
@ -448,6 +489,14 @@
|
||||||
www.mixin(this);
|
www.mixin(this);
|
||||||
this.$hint = $(o.hint);
|
this.$hint = $(o.hint);
|
||||||
this.$input = $(o.input);
|
this.$input = $(o.input);
|
||||||
|
this.$input.attr({
|
||||||
|
"aria-activedescendant": "",
|
||||||
|
"aria-owns": this.$input.attr("id") + "_listbox",
|
||||||
|
role: "combobox",
|
||||||
|
"aria-readonly": "true",
|
||||||
|
"aria-autocomplete": "list"
|
||||||
|
});
|
||||||
|
$(www.menu).attr("id", this.$input.attr("id") + "_listbox");
|
||||||
this.query = this.$input.val();
|
this.query = this.$input.val();
|
||||||
this.queryWhenFocused = this.hasFocus() ? this.query : null;
|
this.queryWhenFocused = this.hasFocus() ? this.query : null;
|
||||||
this.$overflowHelper = buildOverflowHelper(this.$input);
|
this.$overflowHelper = buildOverflowHelper(this.$input);
|
||||||
|
@ -455,6 +504,7 @@
|
||||||
if (this.$hint.length === 0) {
|
if (this.$hint.length === 0) {
|
||||||
this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop;
|
this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop;
|
||||||
}
|
}
|
||||||
|
this.onSync("cursorchange", this._updateDescendent);
|
||||||
}
|
}
|
||||||
Input.normalizeQuery = function(str) {
|
Input.normalizeQuery = function(str) {
|
||||||
return _.toStr(str).replace(/^\s*/g, "").replace(/\s{2,}/g, " ");
|
return _.toStr(str).replace(/^\s*/g, "").replace(/\s{2,}/g, " ");
|
||||||
|
@ -524,6 +574,9 @@
|
||||||
this.trigger("whitespaceChanged", this.query);
|
this.trigger("whitespaceChanged", this.query);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
_updateDescendent: function updateDescendent(event, id) {
|
||||||
|
this.$input.attr("aria-activedescendant", id);
|
||||||
|
},
|
||||||
bind: function() {
|
bind: function() {
|
||||||
var that = this, onBlur, onFocus, onKeydown, onInput;
|
var that = this, onBlur, onFocus, onKeydown, onInput;
|
||||||
onBlur = _.bind(this._onBlur, this);
|
onBlur = _.bind(this._onBlur, this);
|
||||||
|
@ -647,6 +700,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
var keys, nameGenerator;
|
var keys, nameGenerator;
|
||||||
keys = {
|
keys = {
|
||||||
|
dataset: "tt-selectable-dataset",
|
||||||
val: "tt-selectable-display",
|
val: "tt-selectable-display",
|
||||||
obj: "tt-selectable-object"
|
obj: "tt-selectable-object"
|
||||||
};
|
};
|
||||||
|
@ -666,19 +720,20 @@
|
||||||
}
|
}
|
||||||
www.mixin(this);
|
www.mixin(this);
|
||||||
this.highlight = !!o.highlight;
|
this.highlight = !!o.highlight;
|
||||||
this.name = o.name || nameGenerator();
|
this.name = _.toStr(o.name || nameGenerator());
|
||||||
this.limit = o.limit || 5;
|
this.limit = o.limit || 5;
|
||||||
this.displayFn = getDisplayFn(o.display || o.displayKey);
|
this.displayFn = getDisplayFn(o.display || o.displayKey);
|
||||||
this.templates = getTemplates(o.templates, this.displayFn);
|
this.templates = getTemplates(o.templates, this.displayFn);
|
||||||
this.source = o.source.__ttAdapter ? o.source.__ttAdapter() : o.source;
|
this.source = o.source.__ttAdapter ? o.source.__ttAdapter() : o.source;
|
||||||
this.async = _.isUndefined(o.async) ? this.source.length > 2 : !!o.async;
|
this.async = _.isUndefined(o.async) ? this.source.length > 2 : !!o.async;
|
||||||
this._resetLastSuggestion();
|
this._resetLastSuggestion();
|
||||||
this.$el = $(o.node).addClass(this.classes.dataset).addClass(this.classes.dataset + "-" + this.name);
|
this.$el = $(o.node).attr("role", "presentation").addClass(this.classes.dataset).addClass(this.classes.dataset + "-" + this.name);
|
||||||
}
|
}
|
||||||
Dataset.extractData = function extractData(el) {
|
Dataset.extractData = function extractData(el) {
|
||||||
var $el = $(el);
|
var $el = $(el);
|
||||||
if ($el.data(keys.obj)) {
|
if ($el.data(keys.obj)) {
|
||||||
return {
|
return {
|
||||||
|
dataset: $el.data(keys.dataset) || "",
|
||||||
val: $el.data(keys.val) || "",
|
val: $el.data(keys.val) || "",
|
||||||
obj: $el.data(keys.obj) || null
|
obj: $el.data(keys.obj) || null
|
||||||
};
|
};
|
||||||
|
@ -697,7 +752,7 @@
|
||||||
} else {
|
} else {
|
||||||
this._empty();
|
this._empty();
|
||||||
}
|
}
|
||||||
this.trigger("rendered", this.name, suggestions, false);
|
this.trigger("rendered", suggestions, false, this.name);
|
||||||
},
|
},
|
||||||
_append: function append(query, suggestions) {
|
_append: function append(query, suggestions) {
|
||||||
suggestions = suggestions || [];
|
suggestions = suggestions || [];
|
||||||
|
@ -708,7 +763,7 @@
|
||||||
} else if (!this.$lastSuggestion.length && this.templates.notFound) {
|
} else if (!this.$lastSuggestion.length && this.templates.notFound) {
|
||||||
this._renderNotFound(query);
|
this._renderNotFound(query);
|
||||||
}
|
}
|
||||||
this.trigger("rendered", this.name, suggestions, true);
|
this.trigger("rendered", suggestions, true, this.name);
|
||||||
},
|
},
|
||||||
_renderSuggestions: function renderSuggestions(query, suggestions) {
|
_renderSuggestions: function renderSuggestions(query, suggestions) {
|
||||||
var $fragment;
|
var $fragment;
|
||||||
|
@ -749,7 +804,7 @@
|
||||||
_.each(suggestions, function getSuggestionNode(suggestion) {
|
_.each(suggestions, function getSuggestionNode(suggestion) {
|
||||||
var $el, context;
|
var $el, context;
|
||||||
context = that._injectQuery(query, suggestion);
|
context = that._injectQuery(query, suggestion);
|
||||||
$el = $(that.templates.suggestion(context)).data(keys.obj, suggestion).data(keys.val, that.displayFn(suggestion)).addClass(that.classes.suggestion + " " + that.classes.selectable);
|
$el = $(that.templates.suggestion(context)).data(keys.dataset, that.name).data(keys.obj, suggestion).data(keys.val, that.displayFn(suggestion)).addClass(that.classes.suggestion + " " + that.classes.selectable);
|
||||||
fragment.appendChild($el[0]);
|
fragment.appendChild($el[0]);
|
||||||
});
|
});
|
||||||
this.highlight && highlight({
|
this.highlight && highlight({
|
||||||
|
@ -787,7 +842,7 @@
|
||||||
this.cancel = function cancel() {
|
this.cancel = function cancel() {
|
||||||
canceled = true;
|
canceled = true;
|
||||||
that.cancel = $.noop;
|
that.cancel = $.noop;
|
||||||
that.async && that.trigger("asyncCanceled", query);
|
that.async && that.trigger("asyncCanceled", query, that.name);
|
||||||
};
|
};
|
||||||
this.source(query, sync, async);
|
this.source(query, sync, async);
|
||||||
!syncCalled && sync([]);
|
!syncCalled && sync([]);
|
||||||
|
@ -800,16 +855,17 @@
|
||||||
rendered = suggestions.length;
|
rendered = suggestions.length;
|
||||||
that._overwrite(query, suggestions);
|
that._overwrite(query, suggestions);
|
||||||
if (rendered < that.limit && that.async) {
|
if (rendered < that.limit && that.async) {
|
||||||
that.trigger("asyncRequested", query);
|
that.trigger("asyncRequested", query, that.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function async(suggestions) {
|
function async(suggestions) {
|
||||||
suggestions = suggestions || [];
|
suggestions = suggestions || [];
|
||||||
if (!canceled && rendered < that.limit) {
|
if (!canceled && rendered < that.limit) {
|
||||||
that.cancel = $.noop;
|
that.cancel = $.noop;
|
||||||
rendered += suggestions.length;
|
var idx = Math.abs(rendered - that.limit);
|
||||||
that._append(query, suggestions.slice(0, that.limit - rendered));
|
rendered += idx;
|
||||||
that.async && that.trigger("asyncReceived", query);
|
that._append(query, suggestions.slice(0, idx));
|
||||||
|
that.async && that.trigger("asyncReceived", query, that.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -843,7 +899,7 @@
|
||||||
suggestion: templates.suggestion || suggestionTemplate
|
suggestion: templates.suggestion || suggestionTemplate
|
||||||
};
|
};
|
||||||
function suggestionTemplate(context) {
|
function suggestionTemplate(context) {
|
||||||
return $("<div>").text(displayFn(context));
|
return $('<div role="option">').attr("id", _.guid()).text(displayFn(context));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function isValidName(str) {
|
function isValidName(str) {
|
||||||
|
@ -884,10 +940,11 @@
|
||||||
this.trigger.apply(this, arguments);
|
this.trigger.apply(this, arguments);
|
||||||
},
|
},
|
||||||
_allDatasetsEmpty: function allDatasetsEmpty() {
|
_allDatasetsEmpty: function allDatasetsEmpty() {
|
||||||
return _.every(this.datasets, isDatasetEmpty);
|
return _.every(this.datasets, _.bind(function isDatasetEmpty(dataset) {
|
||||||
function isDatasetEmpty(dataset) {
|
var isEmpty = dataset.isEmpty();
|
||||||
return dataset.isEmpty();
|
this.$node.attr("aria-expanded", !isEmpty);
|
||||||
}
|
return isEmpty;
|
||||||
|
}, this));
|
||||||
},
|
},
|
||||||
_getSelectables: function getSelectables() {
|
_getSelectables: function getSelectables() {
|
||||||
return this.$node.find(this.selectors.selectable);
|
return this.$node.find(this.selectors.selectable);
|
||||||
|
@ -912,6 +969,12 @@
|
||||||
var that = this, onSelectableClick;
|
var that = this, onSelectableClick;
|
||||||
onSelectableClick = _.bind(this._onSelectableClick, this);
|
onSelectableClick = _.bind(this._onSelectableClick, this);
|
||||||
this.$node.on("click.tt", this.selectors.selectable, onSelectableClick);
|
this.$node.on("click.tt", this.selectors.selectable, onSelectableClick);
|
||||||
|
this.$node.on("mouseover", this.selectors.selectable, function() {
|
||||||
|
that.setCursor($(this));
|
||||||
|
});
|
||||||
|
this.$node.on("mouseleave", function() {
|
||||||
|
that._removeCursor();
|
||||||
|
});
|
||||||
_.each(this.datasets, function(dataset) {
|
_.each(this.datasets, function(dataset) {
|
||||||
dataset.onSync("asyncRequested", that._propagate, that).onSync("asyncCanceled", that._propagate, that).onSync("asyncReceived", that._propagate, that).onSync("rendered", that._onRendered, that).onSync("cleared", that._onCleared, that);
|
dataset.onSync("asyncRequested", that._propagate, that).onSync("asyncCanceled", that._propagate, that).onSync("asyncReceived", that._propagate, that).onSync("rendered", that._onRendered, that).onSync("cleared", that._onCleared, that);
|
||||||
});
|
});
|
||||||
|
@ -921,9 +984,11 @@
|
||||||
return this.$node.hasClass(this.classes.open);
|
return this.$node.hasClass(this.classes.open);
|
||||||
},
|
},
|
||||||
open: function open() {
|
open: function open() {
|
||||||
|
this.$node.scrollTop(0);
|
||||||
this.$node.addClass(this.classes.open);
|
this.$node.addClass(this.classes.open);
|
||||||
},
|
},
|
||||||
close: function close() {
|
close: function close() {
|
||||||
|
this.$node.attr("aria-expanded", false);
|
||||||
this.$node.removeClass(this.classes.open);
|
this.$node.removeClass(this.classes.open);
|
||||||
this._removeCursor();
|
this._removeCursor();
|
||||||
},
|
},
|
||||||
|
@ -988,6 +1053,55 @@
|
||||||
});
|
});
|
||||||
return Menu;
|
return Menu;
|
||||||
}();
|
}();
|
||||||
|
var Status = function() {
|
||||||
|
"use strict";
|
||||||
|
function Status(options) {
|
||||||
|
this.$el = $("<span></span>", {
|
||||||
|
role: "status",
|
||||||
|
"aria-live": "polite"
|
||||||
|
}).css({
|
||||||
|
position: "absolute",
|
||||||
|
padding: "0",
|
||||||
|
border: "0",
|
||||||
|
height: "1px",
|
||||||
|
width: "1px",
|
||||||
|
"margin-bottom": "-1px",
|
||||||
|
"margin-right": "-1px",
|
||||||
|
overflow: "hidden",
|
||||||
|
clip: "rect(0 0 0 0)",
|
||||||
|
"white-space": "nowrap"
|
||||||
|
});
|
||||||
|
options.$input.after(this.$el);
|
||||||
|
_.each(options.menu.datasets, _.bind(function(dataset) {
|
||||||
|
if (dataset.onSync) {
|
||||||
|
dataset.onSync("rendered", _.bind(this.update, this));
|
||||||
|
dataset.onSync("cleared", _.bind(this.cleared, this));
|
||||||
|
}
|
||||||
|
}, this));
|
||||||
|
}
|
||||||
|
_.mixin(Status.prototype, {
|
||||||
|
update: function update(event, suggestions) {
|
||||||
|
var length = suggestions.length;
|
||||||
|
var words;
|
||||||
|
if (length === 1) {
|
||||||
|
words = {
|
||||||
|
result: "result",
|
||||||
|
is: "is"
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
words = {
|
||||||
|
result: "results",
|
||||||
|
is: "are"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.$el.text(length + " " + words.result + " " + words.is + " available, use up and down arrow keys to navigate.");
|
||||||
|
},
|
||||||
|
cleared: function() {
|
||||||
|
this.$el.text("");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return Status;
|
||||||
|
}();
|
||||||
var DefaultMenu = function() {
|
var DefaultMenu = function() {
|
||||||
"use strict";
|
"use strict";
|
||||||
var s = Menu.prototype;
|
var s = Menu.prototype;
|
||||||
|
@ -1052,6 +1166,7 @@
|
||||||
this.input = o.input;
|
this.input = o.input;
|
||||||
this.menu = o.menu;
|
this.menu = o.menu;
|
||||||
this.enabled = true;
|
this.enabled = true;
|
||||||
|
this.autoselect = !!o.autoselect;
|
||||||
this.active = false;
|
this.active = false;
|
||||||
this.input.hasFocus() && this.activate();
|
this.input.hasFocus() && this.activate();
|
||||||
this.dir = this.input.getLangDir();
|
this.dir = this.input.getLangDir();
|
||||||
|
@ -1098,8 +1213,12 @@
|
||||||
_onDatasetCleared: function onDatasetCleared() {
|
_onDatasetCleared: function onDatasetCleared() {
|
||||||
this._updateHint();
|
this._updateHint();
|
||||||
},
|
},
|
||||||
_onDatasetRendered: function onDatasetRendered(type, dataset, suggestions, async) {
|
_onDatasetRendered: function onDatasetRendered(type, suggestions, async, dataset) {
|
||||||
this._updateHint();
|
this._updateHint();
|
||||||
|
if (this.autoselect) {
|
||||||
|
var cursorClass = this.selectors.cursor.substr(1);
|
||||||
|
this.menu.$node.find(this.selectors.suggestion).first().addClass(cursorClass);
|
||||||
|
}
|
||||||
this.eventBus.trigger("render", suggestions, async, dataset);
|
this.eventBus.trigger("render", suggestions, async, dataset);
|
||||||
},
|
},
|
||||||
_onAsyncRequested: function onAsyncRequested(type, dataset, query) {
|
_onAsyncRequested: function onAsyncRequested(type, dataset, query) {
|
||||||
|
@ -1122,7 +1241,15 @@
|
||||||
_onEnterKeyed: function onEnterKeyed(type, $e) {
|
_onEnterKeyed: function onEnterKeyed(type, $e) {
|
||||||
var $selectable;
|
var $selectable;
|
||||||
if ($selectable = this.menu.getActiveSelectable()) {
|
if ($selectable = this.menu.getActiveSelectable()) {
|
||||||
this.select($selectable) && $e.preventDefault();
|
if (this.select($selectable)) {
|
||||||
|
$e.preventDefault();
|
||||||
|
$e.stopPropagation();
|
||||||
|
}
|
||||||
|
} else if (this.autoselect) {
|
||||||
|
if (this.select(this.menu.getTopSelectable())) {
|
||||||
|
$e.preventDefault();
|
||||||
|
$e.stopPropagation();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_onTabKeyed: function onTabKeyed(type, $e) {
|
_onTabKeyed: function onTabKeyed(type, $e) {
|
||||||
|
@ -1144,12 +1271,12 @@
|
||||||
},
|
},
|
||||||
_onLeftKeyed: function onLeftKeyed() {
|
_onLeftKeyed: function onLeftKeyed() {
|
||||||
if (this.dir === "rtl" && this.input.isCursorAtEnd()) {
|
if (this.dir === "rtl" && this.input.isCursorAtEnd()) {
|
||||||
this.autocomplete(this.menu.getTopSelectable());
|
this.autocomplete(this.menu.getActiveSelectable() || this.menu.getTopSelectable());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_onRightKeyed: function onRightKeyed() {
|
_onRightKeyed: function onRightKeyed() {
|
||||||
if (this.dir === "ltr" && this.input.isCursorAtEnd()) {
|
if (this.dir === "ltr" && this.input.isCursorAtEnd()) {
|
||||||
this.autocomplete(this.menu.getTopSelectable());
|
this.autocomplete(this.menu.getActiveSelectable() || this.menu.getTopSelectable());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_onQueryChanged: function onQueryChanged(e, query) {
|
_onQueryChanged: function onQueryChanged(e, query) {
|
||||||
|
@ -1249,9 +1376,9 @@
|
||||||
},
|
},
|
||||||
select: function select($selectable) {
|
select: function select($selectable) {
|
||||||
var data = this.menu.getSelectableData($selectable);
|
var data = this.menu.getSelectableData($selectable);
|
||||||
if (data && !this.eventBus.before("select", data.obj)) {
|
if (data && !this.eventBus.before("select", data.obj, data.dataset)) {
|
||||||
this.input.setQuery(data.val, true);
|
this.input.setQuery(data.val, true);
|
||||||
this.eventBus.trigger("select", data.obj);
|
this.eventBus.trigger("select", data.obj, data.dataset);
|
||||||
this.close();
|
this.close();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1262,21 +1389,24 @@
|
||||||
query = this.input.getQuery();
|
query = this.input.getQuery();
|
||||||
data = this.menu.getSelectableData($selectable);
|
data = this.menu.getSelectableData($selectable);
|
||||||
isValid = data && query !== data.val;
|
isValid = data && query !== data.val;
|
||||||
if (isValid && !this.eventBus.before("autocomplete", data.obj)) {
|
if (isValid && !this.eventBus.before("autocomplete", data.obj, data.dataset)) {
|
||||||
this.input.setQuery(data.val);
|
this.input.setQuery(data.val);
|
||||||
this.eventBus.trigger("autocomplete", data.obj);
|
this.eventBus.trigger("autocomplete", data.obj, data.dataset);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
moveCursor: function moveCursor(delta) {
|
moveCursor: function moveCursor(delta) {
|
||||||
var query, $candidate, data, payload, cancelMove;
|
var query, $candidate, data, suggestion, datasetName, cancelMove, id;
|
||||||
query = this.input.getQuery();
|
query = this.input.getQuery();
|
||||||
$candidate = this.menu.selectableRelativeToCursor(delta);
|
$candidate = this.menu.selectableRelativeToCursor(delta);
|
||||||
data = this.menu.getSelectableData($candidate);
|
data = this.menu.getSelectableData($candidate);
|
||||||
payload = data ? data.obj : null;
|
suggestion = data ? data.obj : null;
|
||||||
|
datasetName = data ? data.dataset : null;
|
||||||
|
id = $candidate ? $candidate.attr("id") : null;
|
||||||
|
this.input.trigger("cursorchange", id);
|
||||||
cancelMove = this._minLengthMet() && this.menu.update(query);
|
cancelMove = this._minLengthMet() && this.menu.update(query);
|
||||||
if (!cancelMove && !this.eventBus.before("cursorchange", payload)) {
|
if (!cancelMove && !this.eventBus.before("cursorchange", suggestion, datasetName)) {
|
||||||
this.menu.setCursor($candidate);
|
this.menu.setCursor($candidate);
|
||||||
if (data) {
|
if (data) {
|
||||||
this.input.setInputValue(data.val);
|
this.input.setInputValue(data.val);
|
||||||
|
@ -1284,7 +1414,7 @@
|
||||||
this.input.resetInputValue();
|
this.input.resetInputValue();
|
||||||
this._updateHint();
|
this._updateHint();
|
||||||
}
|
}
|
||||||
this.eventBus.trigger("cursorchange", payload);
|
this.eventBus.trigger("cursorchange", suggestion, datasetName);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -1322,7 +1452,7 @@
|
||||||
www = WWW(o.classNames);
|
www = WWW(o.classNames);
|
||||||
return this.each(attach);
|
return this.each(attach);
|
||||||
function attach() {
|
function attach() {
|
||||||
var $input, $wrapper, $hint, $menu, defaultHint, defaultMenu, eventBus, input, menu, typeahead, MenuConstructor;
|
var $input, $wrapper, $hint, $menu, defaultHint, defaultMenu, eventBus, input, menu, status, typeahead, MenuConstructor;
|
||||||
_.each(datasets, function(d) {
|
_.each(datasets, function(d) {
|
||||||
d.highlight = !!o.highlight;
|
d.highlight = !!o.highlight;
|
||||||
});
|
});
|
||||||
|
@ -1353,11 +1483,16 @@
|
||||||
node: $menu,
|
node: $menu,
|
||||||
datasets: datasets
|
datasets: datasets
|
||||||
}, www);
|
}, www);
|
||||||
|
status = new Status({
|
||||||
|
$input: $input,
|
||||||
|
menu: menu
|
||||||
|
});
|
||||||
typeahead = new Typeahead({
|
typeahead = new Typeahead({
|
||||||
input: input,
|
input: input,
|
||||||
menu: menu,
|
menu: menu,
|
||||||
eventBus: eventBus,
|
eventBus: eventBus,
|
||||||
minLength: o.minLength
|
minLength: o.minLength,
|
||||||
|
autoselect: o.autoselect
|
||||||
}, www);
|
}, www);
|
||||||
$input.data(keys.www, www);
|
$input.data(keys.www, www);
|
||||||
$input.data(keys.typeahead, typeahead);
|
$input.data(keys.typeahead, typeahead);
|
||||||
|
@ -1450,7 +1585,7 @@
|
||||||
return query;
|
return query;
|
||||||
} else {
|
} else {
|
||||||
ttEach(this, function(t) {
|
ttEach(this, function(t) {
|
||||||
t.setVal(newVal);
|
t.setVal(_.toStr(newVal));
|
||||||
});
|
});
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -1481,8 +1616,10 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function buildHintFromInput($input, www) {
|
function buildHintFromInput($input, www) {
|
||||||
return $input.clone().addClass(www.classes.hint).removeData().css(www.css.hint).css(getBackgroundStyles($input)).prop("readonly", true).removeAttr("id name placeholder required").attr({
|
return $input.clone().addClass(www.classes.hint).removeData().css(www.css.hint).css(getBackgroundStyles($input)).prop({
|
||||||
autocomplete: "off",
|
readonly: true,
|
||||||
|
required: false
|
||||||
|
}).removeAttr("id name placeholder").removeClass("required").attr({
|
||||||
spellcheck: "false",
|
spellcheck: "false",
|
||||||
tabindex: -1
|
tabindex: -1
|
||||||
});
|
});
|
||||||
|
@ -1495,7 +1632,6 @@
|
||||||
style: $input.attr("style")
|
style: $input.attr("style")
|
||||||
});
|
});
|
||||||
$input.addClass(www.classes.input).attr({
|
$input.addClass(www.classes.input).attr({
|
||||||
autocomplete: "off",
|
|
||||||
spellcheck: false
|
spellcheck: false
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
|
|
41
resources/assets/js/timeline.js
vendored
41
resources/assets/js/timeline.js
vendored
|
@ -1,13 +1,54 @@
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$('.pagination').hide();
|
$('.pagination').hide();
|
||||||
|
$('.container.timeline-container').removeClass('d-none');
|
||||||
let elem = document.querySelector('.timeline-feed');
|
let elem = document.querySelector('.timeline-feed');
|
||||||
|
pixelfed.fetchLikes();
|
||||||
|
|
||||||
let infScroll = new InfiniteScroll( elem, {
|
let infScroll = new InfiniteScroll( elem, {
|
||||||
path: '.pagination__next',
|
path: '.pagination__next',
|
||||||
append: '.timeline-feed',
|
append: '.timeline-feed',
|
||||||
status: '.page-load-status',
|
status: '.page-load-status',
|
||||||
history: false,
|
history: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
infScroll.on( 'append', function( response, path, items ) {
|
infScroll.on( 'append', function( response, path, items ) {
|
||||||
pixelfed.hydrateLikes();
|
pixelfed.hydrateLikes();
|
||||||
|
$('.status-card > .card-footer').each(function() {
|
||||||
|
var el = $(this);
|
||||||
|
if(!el.hasClass('d-none') && !el.find('input[name="comment"]').val()) {
|
||||||
|
$(this).addClass('d-none');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on("DOMContentLoaded", function() {
|
||||||
|
|
||||||
|
var active = false;
|
||||||
|
|
||||||
|
var lazyLoad = function() {
|
||||||
|
if (active === false) {
|
||||||
|
active = true;
|
||||||
|
|
||||||
|
var lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));
|
||||||
|
lazyImages.forEach(function(lazyImage) {
|
||||||
|
if ((lazyImage.getBoundingClientRect().top <= window.innerHeight && lazyImage.getBoundingClientRect().bottom >= 0) && getComputedStyle(lazyImage).display !== "none") {
|
||||||
|
lazyImage.src = lazyImage.dataset.src;
|
||||||
|
lazyImage.srcset = lazyImage.dataset.srcset;
|
||||||
|
lazyImage.classList.remove("lazy");
|
||||||
|
|
||||||
|
lazyImages = lazyImages.filter(function(image) {
|
||||||
|
return image !== lazyImage;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
active = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
document.addEventListener("scroll", lazyLoad);
|
||||||
|
window.addEventListener("resize", lazyLoad);
|
||||||
|
window.addEventListener("orientationchange", lazyLoad);
|
||||||
});
|
});
|
||||||
|
|
41
resources/assets/sass/custom.scss
vendored
41
resources/assets/sass/custom.scss
vendored
|
@ -173,11 +173,6 @@ body, button, input, textarea {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.fas.fa-heart {
|
|
||||||
color: #f70ec4!important;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@media (max-width: map-get($grid-breakpoints, "md")) {
|
@media (max-width: map-get($grid-breakpoints, "md")) {
|
||||||
.border-md-left-0 {
|
.border-md-left-0 {
|
||||||
border-left:0!important
|
border-left:0!important
|
||||||
|
@ -194,6 +189,10 @@ body, button, input, textarea {
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: map-get($grid-breakpoints, "sm")) {
|
@media (max-width: map-get($grid-breakpoints, "sm")) {
|
||||||
|
.card-md-border-0 {
|
||||||
|
border-width: 0!important;
|
||||||
|
border-radius: 0!important;
|
||||||
|
}
|
||||||
.card-md-rounded-0 {
|
.card-md-rounded-0 {
|
||||||
border-width: 1px 0;
|
border-width: 1px 0;
|
||||||
border-radius:0 !important;
|
border-radius:0 !important;
|
||||||
|
@ -263,3 +262,35 @@ body, button, input, textarea {
|
||||||
animation-duration: 0.5s;
|
animation-duration: 0.5s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
box-shadow: 0 2px 6px 0 hsla(0, 0%, 0%, 0.2);
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-shadow {
|
||||||
|
box-shadow: 0 2px 6px 0 hsla(0, 0%, 0%, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-left-primary {
|
||||||
|
border-left: 3px solid $primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-nav .nav-item.active .nav-link {
|
||||||
|
font-weight: bold !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
details summary::-webkit-details-marker {
|
||||||
|
display: none!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-animated > summary {
|
||||||
|
display: block;
|
||||||
|
background-color: #ECF0F1;
|
||||||
|
padding-top: 50px;
|
||||||
|
padding-bottom: 50px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-animated[open] > summary {
|
||||||
|
display: none!important;
|
||||||
|
}
|
||||||
|
|
|
@ -9,5 +9,6 @@ return [
|
||||||
'settings' => 'Einstellungen',
|
'settings' => 'Einstellungen',
|
||||||
'admin' => 'Administration',
|
'admin' => 'Administration',
|
||||||
'logout' => 'Abmelden',
|
'logout' => 'Abmelden',
|
||||||
|
'directMessages' => 'Privatnachrichten',
|
||||||
|
|
||||||
];
|
];
|
|
@ -9,5 +9,6 @@ return [
|
||||||
'settings' => 'Settings',
|
'settings' => 'Settings',
|
||||||
'admin' => 'Admin',
|
'admin' => 'Admin',
|
||||||
'logout' => 'Logout',
|
'logout' => 'Logout',
|
||||||
|
'directMessages' => 'Direct Messages',
|
||||||
|
|
||||||
];
|
];
|
10
resources/lang/fr/navmenu.php
Normal file
10
resources/lang/fr/navmenu.php
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
return [
|
||||||
|
'viewMyProfile' => 'Voir mon profil',
|
||||||
|
'myTimeline' => 'Ma chronologie',
|
||||||
|
'publicTimeline' => 'Chronologie publique',
|
||||||
|
'remoteFollow' => 'Suivre à distance',
|
||||||
|
'settings' => 'Paramètres',
|
||||||
|
'admin' => 'Admin',
|
||||||
|
'logout' => ' Se déconnecter',
|
||||||
|
];
|
|
@ -2,4 +2,6 @@
|
||||||
return [
|
return [
|
||||||
'likedPhoto' => 'a aimé votre photo.',
|
'likedPhoto' => 'a aimé votre photo.',
|
||||||
'startedFollowingYou' => 'a commencé à vous suivre.',
|
'startedFollowingYou' => 'a commencé à vous suivre.',
|
||||||
|
'commented' => 'commenté sur votre post.',
|
||||||
|
'mentionedYou' => 'vous à mentionné.'
|
||||||
];
|
];
|
||||||
|
|
13
resources/lang/gl/navmenu.php
Normal file
13
resources/lang/gl/navmenu.php
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'viewMyProfile' => 'Ver perfil',
|
||||||
|
'myTimeline' => 'A miña liña temporal',
|
||||||
|
'publicTimeline' => 'Liña temporal pública',
|
||||||
|
'remoteFollow' => 'Seguimento remoto',
|
||||||
|
'settings' => 'Axustes',
|
||||||
|
'admin' => 'Admin',
|
||||||
|
'logout' => 'Saír',
|
||||||
|
|
||||||
|
];
|
13
resources/lang/oc/navmenu.php
Normal file
13
resources/lang/oc/navmenu.php
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'viewMyProfile' => 'Veire mon perfil',
|
||||||
|
'myTimeline' => 'Ma cronologia',
|
||||||
|
'publicTimeline' => 'Cronologia publica',
|
||||||
|
'remoteFollow' => 'Seguir a distància',
|
||||||
|
'settings' => 'Paramètres',
|
||||||
|
'admin' => 'Admin',
|
||||||
|
'logout' => 'Desconnexion',
|
||||||
|
|
||||||
|
];
|
|
@ -4,5 +4,7 @@ return [
|
||||||
|
|
||||||
'likedPhoto' => 'a aimat vòstra fòto.',
|
'likedPhoto' => 'a aimat vòstra fòto.',
|
||||||
'startedFollowingYou' => 'a començat de vos seguir.',
|
'startedFollowingYou' => 'a començat de vos seguir.',
|
||||||
|
'commented' => 'a comentat vòstra publicacion.',
|
||||||
|
'mentionedYou' => 'vos a mencionat.'
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'emptyTimeline' => 'Aqueste utilizaire a pas encara de publicacion !',
|
'emptyTimeline' => 'Aqueste utilizaire a pas encara de publicacion !',
|
||||||
|
'emptyFollowers' => 'Aqueste utilizaire a pas encara pas seguidors !',
|
||||||
|
'emptyFollowing' => 'Aqueste utilizaire sèc degun pel moment !',
|
||||||
|
'savedWarning' => 'Solament vos vesètz çò que salvagardatz',
|
||||||
];
|
];
|
||||||
|
|
7
resources/lang/oc/timeline.php
Normal file
7
resources/lang/oc/timeline.php
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'emptyPersonalTimeline' => 'Vòstre cronologia es voida.'
|
||||||
|
|
||||||
|
];
|
13
resources/lang/pl/navmenu.php
Normal file
13
resources/lang/pl/navmenu.php
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'viewMyProfile' => 'Pokaż mój profil',
|
||||||
|
'myTimeline' => 'Moja oś czasu',
|
||||||
|
'publicTimeline' => 'Publiczna oś czasu',
|
||||||
|
'remoteFollow' => 'Zdalne śledzenie',
|
||||||
|
'settings' => 'Ustawienia',
|
||||||
|
'admin' => 'Administrator',
|
||||||
|
'logout' => 'Wyloguj się',
|
||||||
|
|
||||||
|
];
|
|
@ -4,5 +4,7 @@ return [
|
||||||
|
|
||||||
'likedPhoto' => 'polubił Twoje zdjęcie.',
|
'likedPhoto' => 'polubił Twoje zdjęcie.',
|
||||||
'startedFollowingYou' => 'zaczął Cię obserwować.',
|
'startedFollowingYou' => 'zaczął Cię obserwować.',
|
||||||
|
'commented' => 'skomentował Twój wpis',
|
||||||
|
'mentionedYou' => 'wspomniał o Tobie.'
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
35
resources/lang/vendor/backup/gl/notifications.php
vendored
Normal file
35
resources/lang/vendor/backup/gl/notifications.php
vendored
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'exception_message' => 'Mensaxe da exepción: :message',
|
||||||
|
'exception_trace' => 'Traza da excepción: :trace',
|
||||||
|
'exception_message_title' => 'Mensaxe da excepción',
|
||||||
|
'exception_trace_title' => 'Traza da excepción',
|
||||||
|
|
||||||
|
'backup_failed_subject' => 'Erro no respaldo de :application_name',
|
||||||
|
'backup_failed_body' => 'Importante: Algo fallou ao respaldar :application_name',
|
||||||
|
|
||||||
|
'backup_successful_subject' => 'Respaldo realizado correctamente :application_name',
|
||||||
|
'backup_successful_subject_title' => 'Novo respaldo correcto!',
|
||||||
|
'backup_successful_body' => 'Parabéns, un novo respaldo de :application_name foi realizado correctamente no disco con nome :disk_name.',
|
||||||
|
|
||||||
|
'cleanup_failed_subject' => 'Limpando os respaldos de :application_name failed.',
|
||||||
|
'cleanup_failed_body' => 'Algo fallou mentras se limpaban os respaldos de :application_name',
|
||||||
|
|
||||||
|
'cleanup_successful_subject' => 'Limpeza correcta nos respaldos de :application_name',
|
||||||
|
'cleanup_successful_subject_title' => 'Limpeza dos respaldos correcta!',
|
||||||
|
'cleanup_successful_body' => 'Realizouse correctamente a limpeza dos respaldos de :application_name no disco con nome :disk_name.',
|
||||||
|
|
||||||
|
'healthy_backup_found_subject' => 'Os respaldos de :application_name no disco :disk_name están en bo estado',
|
||||||
|
'healthy_backup_found_subject_title' => 'Os respaldos de :application_name están ben!',
|
||||||
|
'healthy_backup_found_body' => 'Os respaldos de :application_name están en bo estado. Bo traballo!',
|
||||||
|
|
||||||
|
'unhealthy_backup_found_subject' => 'Importante: Os respaldos de :application_name non están en bo estado',
|
||||||
|
'unhealthy_backup_found_subject_title' => 'Importante: Os respaldos de :application_name non están ben. :problem',
|
||||||
|
'unhealthy_backup_found_body' => 'Os respaldos para :application_name no disco :disk_name non están ben.',
|
||||||
|
'unhealthy_backup_found_not_reachable' => 'Non se puido alcanzar o disco de destino. :error',
|
||||||
|
'unhealthy_backup_found_empty' => 'Non existen copias de respaldo para esta aplicación.',
|
||||||
|
'unhealthy_backup_found_old' => 'O último respaldo realizouse en :date e considerase demasiado antigo.',
|
||||||
|
'unhealthy_backup_found_unknown' => 'Lamentámolo, non se puido determinar unha causa concreta.',
|
||||||
|
'unhealthy_backup_found_full' => 'Os respaldos están a utilizar demasiado espazo. A utilización actual de :disk_usage é maior que o límite establecido de :disk_limit.',
|
||||||
|
];
|
|
@ -3,16 +3,53 @@
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="container notification-page" style="min-height: 60vh;">
|
<div class="container notification-page" style="min-height: 60vh;">
|
||||||
<div class="col-12 col-md-8 offset-md-2">
|
<div class="col-12 col-md-8 offset-md-2">
|
||||||
|
<div class="card mt-3">
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<ul class="nav nav-tabs d-flex text-center">
|
||||||
|
{{--
|
||||||
|
<li class="nav-item flex-fill">
|
||||||
|
<a class="nav-link font-weight-bold text-uppercase" href="{{route('notifications.following')}}">Following</a>
|
||||||
|
</li>
|
||||||
|
--}}
|
||||||
|
<li class="nav-item flex-fill">
|
||||||
|
<a class="nav-link font-weight-bold text-uppercase active" href="{{route('notifications')}}">My Notifications</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="">
|
||||||
|
<div class="dropdown text-right mt-2">
|
||||||
|
<a class="btn btn-link btn-sm dropdown-toggle font-weight-bold text-dark" href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
Filter
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="dropdown-menu" aria-labelledby="dropdownMenuLink">
|
||||||
|
<a href="?a=comment" class="dropdown-item font-weight-bold" title="Commented on your post">
|
||||||
|
Comments only
|
||||||
|
</a>
|
||||||
|
<a href="?a=follow" class="dropdown-item font-weight-bold" title="Followed you">
|
||||||
|
New Followers only
|
||||||
|
</a>
|
||||||
|
<a href="?a=mention" class="dropdown-item font-weight-bold" title="Mentioned you">
|
||||||
|
Mentions only
|
||||||
|
</a>
|
||||||
|
<a href="{{route('notifications')}}" class="dropdown-item font-weight-bold text-dark">
|
||||||
|
View All
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
|
|
||||||
@if($notifications->count() > 0)
|
@if($notifications->count() > 0)
|
||||||
@foreach($notifications as $notification)
|
@foreach($notifications as $notification)
|
||||||
<li class="list-group-item notification">
|
<li class="list-group-item notification border-0">
|
||||||
@switch($notification->action)
|
@switch($notification->action)
|
||||||
|
|
||||||
@case('like')
|
@case('like')
|
||||||
<span class="notification-icon pr-3">
|
<span class="notification-icon pr-3">
|
||||||
<img src="{{$notification->actor->avatarUrl()}}" width="32px" class="rounded-circle">
|
<img src="{{optional($notification->actor, function($actor) {
|
||||||
|
return $actor->avatarUrl(); }) }}" width="32px" class="rounded-circle">
|
||||||
</span>
|
</span>
|
||||||
<span class="notification-text">
|
<span class="notification-text">
|
||||||
{!! $notification->rendered !!}
|
{!! $notification->rendered !!}
|
||||||
|
|
96
resources/views/account/following.blade.php
Normal file
96
resources/views/account/following.blade.php
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="container notification-page" style="min-height: 60vh;">
|
||||||
|
<div class="col-12 col-md-8 offset-md-2">
|
||||||
|
<div class="card mt-3">
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<ul class="nav nav-tabs d-flex text-center">
|
||||||
|
<li class="nav-item flex-fill">
|
||||||
|
<a class="nav-link font-weight-bold text-uppercase active" href="{{route('notifications.following')}}">Following</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item flex-fill">
|
||||||
|
<a class="nav-link font-weight-bold text-uppercase" href="{{route('notifications')}}">My Notifications</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="">
|
||||||
|
{{-- <div class="card-header bg-white">
|
||||||
|
<span class="font-weight-bold lead">Notifications</span>
|
||||||
|
<span class="small float-right font-weight-bold">
|
||||||
|
<a href="?a=comment" class="pr-4 text-muted" title="Commented on your post"><i class="fas fa-comment fa-2x"></i></a>
|
||||||
|
<a href="?a=follow" class="pr-4 text-muted" title="Followed you"><i class="fas fa-user-plus fa-2x"></i></a>
|
||||||
|
<a href="?a=mention" class="pr-4 text-muted" title="Mentioned you"><i class="fas fa-comment-dots fa-2x"></i></a>
|
||||||
|
<a href="{{route('notifications')}}" class="font-weight-bold text-dark">View All</a>
|
||||||
|
</span>
|
||||||
|
</div> --}}
|
||||||
|
</div>
|
||||||
|
<ul class="list-group">
|
||||||
|
|
||||||
|
@if($notifications->count() > 0)
|
||||||
|
@foreach($notifications as $notification)
|
||||||
|
@php
|
||||||
|
if(!in_array($notification->action, ['like', 'follow'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
@endphp
|
||||||
|
<li class="list-group-item notification border-0">
|
||||||
|
@switch($notification->action)
|
||||||
|
|
||||||
|
@case('like')
|
||||||
|
<span class="notification-icon pr-3">
|
||||||
|
<img src="{{optional($notification->actor, function($actor) {
|
||||||
|
return $actor->avatarUrl(); }) }}" width="32px" class="rounded-circle">
|
||||||
|
</span>
|
||||||
|
<span class="notification-text">
|
||||||
|
<a class="font-weight-bold text-dark" href="{{$notification->actor->url()}}">{{$notification->actor->username}}</a>
|
||||||
|
|
||||||
|
{{__('liked a post by')}}
|
||||||
|
|
||||||
|
<a class="font-weight-bold text-dark" href="{{$notification->item->profile->url()}}">{{$notification->item->profile->username}}</a>
|
||||||
|
|
||||||
|
<span class="text-muted notification-timestamp pl-1">{{$notification->created_at->diffForHumans(null, true, true, true)}}</span>
|
||||||
|
</span>
|
||||||
|
<span class="float-right notification-action">
|
||||||
|
@if($notification->item_id && $notification->item_type == 'App\Status')
|
||||||
|
<a href="{{$notification->status->url()}}"><img src="{{$notification->status->thumb()}}" width="32px" height="32px"></a>
|
||||||
|
@endif
|
||||||
|
</span>
|
||||||
|
@break
|
||||||
|
|
||||||
|
@case('follow')
|
||||||
|
<span class="notification-icon pr-3">
|
||||||
|
<img src="{{$notification->actor->avatarUrl()}}" width="32px" class="rounded-circle">
|
||||||
|
</span>
|
||||||
|
<span class="notification-text">
|
||||||
|
<a class="font-weight-bold text-dark" href="{{$notification->actor->url()}}">{{$notification->actor->username}}</a>
|
||||||
|
|
||||||
|
{{__('started following')}}
|
||||||
|
|
||||||
|
<a class="font-weight-bold text-dark" href="{{$notification->item->url()}}">{{$notification->item->username}}</a>
|
||||||
|
|
||||||
|
<span class="text-muted notification-timestamp pl-1">{{$notification->created_at->diffForHumans(null, true, true, true)}}</span>
|
||||||
|
</span>
|
||||||
|
@break
|
||||||
|
|
||||||
|
@endswitch
|
||||||
|
</li>
|
||||||
|
@endforeach
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-center my-4">
|
||||||
|
{{$notifications->links()}}
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div class="mt-4">
|
||||||
|
<div class="alert alert-info font-weight-bold">No unread notifications found.</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script type="text/javascript" src="{{mix('js/activity.js')}}"></script>
|
||||||
|
@endpush
|
|
@ -26,14 +26,14 @@
|
||||||
<link rel="self" type="application/atom+xml" href="{{$profile->permalink('.atom')}}"/>
|
<link rel="self" type="application/atom+xml" href="{{$profile->permalink('.atom')}}"/>
|
||||||
@foreach($items as $item)
|
@foreach($items as $item)
|
||||||
<entry>
|
<entry>
|
||||||
<title><![CDATA[{{ $item->caption }}]]></title>
|
<title>{{ $item->caption }}</title>
|
||||||
<link rel="alternate" href="{{ $item->url() }}" />
|
<link rel="alternate" href="{{ $item->url() }}" />
|
||||||
<id>{{ url($item->id) }}</id>
|
<id>{{ url($item->id) }}</id>
|
||||||
<author>
|
<author>
|
||||||
<name> <![CDATA[{{ $item->profile->username }}]]></name>
|
<name> <![CDATA[{{ $item->profile->username }}]]></name>
|
||||||
</author>
|
</author>
|
||||||
<summary type="html">
|
<summary type="html">
|
||||||
<![CDATA[{!! $item->caption !!}]]>
|
{{ $item->caption }}
|
||||||
</summary>
|
</summary>
|
||||||
<updated>{{ $item->updated_at->toAtomString() }}</updated>
|
<updated>{{ $item->updated_at->toAtomString() }}</updated>
|
||||||
</entry>
|
</entry>
|
||||||
|
|
|
@ -6,16 +6,16 @@
|
||||||
<p class="lead text-muted font-weight-bold">Discover People</p>
|
<p class="lead text-muted font-weight-bold">Discover People</p>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@foreach($people as $profile)
|
@foreach($people as $profile)
|
||||||
<div class="col-md-4">
|
<div class="col-4 p-0 p-sm-2 p-md-3">
|
||||||
<div class="card">
|
<div class="card card-md-border-0">
|
||||||
<div class="card-body p-4 text-center">
|
<div class="card-body p-4 text-center">
|
||||||
<div class="avatar pb-3">
|
<div class="avatar pb-3">
|
||||||
<a href="{{$profile->url()}}">
|
<a href="{{$profile->url()}}">
|
||||||
<img src="{{$profile->avatarUrl()}}" class="img-thumbnail rounded-circle" width="64px">
|
<img src="{{$profile->avatarUrl()}}" class="img-thumbnail rounded-circle" width="64px">
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<p class="lead font-weight-bold mb-0"><a href="{{$profile->url()}}" class="text-dark">{{$profile->username}}</a></p>
|
<p class="lead font-weight-bold mb-0 text-truncate"><a href="{{$profile->url()}}" class="text-dark">{{$profile->username}}</a></p>
|
||||||
<p class="text-muted">{{$profile->name}}</p>
|
<p class="text-muted text-truncate">{{$profile->name}}</p>
|
||||||
<form class="follow-form" method="post" action="/i/follow" data-id="{{$profile->id}}" data-action="follow">
|
<form class="follow-form" method="post" action="/i/follow" data-id="{{$profile->id}}" data-action="follow">
|
||||||
@csrf
|
@csrf
|
||||||
<input type="hidden" name="item" value="{{$profile->id}}">
|
<input type="hidden" name="item" value="{{$profile->id}}">
|
||||||
|
@ -31,9 +31,21 @@
|
||||||
<p class="lead text-muted font-weight-bold">Explore</p>
|
<p class="lead text-muted font-weight-bold">Explore</p>
|
||||||
<div class="profile-timeline row">
|
<div class="profile-timeline row">
|
||||||
@foreach($posts as $status)
|
@foreach($posts as $status)
|
||||||
<div class="col-12 col-md-4 mb-4">
|
<div class="col-4 p-0 p-sm-2 p-md-3">
|
||||||
<a class="card" href="{{$status->url()}}">
|
<a class="card info-overlay card-md-border-0" href="{{$status->url()}}">
|
||||||
<img class="card-img-top" src="{{$status->thumb()}}" width="300px" height="300px">
|
<div class="square {{$status->firstMedia()->filter_class}}">
|
||||||
|
<div class="square-content" style="background-image: url('{{$status->thumb()}}')"></div>
|
||||||
|
<div class="info-overlay-text">
|
||||||
|
<h5 class="text-white m-auto font-weight-bold">
|
||||||
|
<span class="pr-4">
|
||||||
|
<span class="far fa-heart fa-lg pr-1"></span> {{$status->likes_count}}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<span class="far fa-comment fa-lg pr-1"></span> {{$status->comments_count}}
|
||||||
|
</span>
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<div class="profile-header row my-5">
|
<div class="profile-header row my-5">
|
||||||
<div class="col-12 col-md-3">
|
<div class="col-12 col-md-3">
|
||||||
<div class="profile-avatar">
|
<div class="profile-avatar">
|
||||||
<img class="img-thumbnail" src="https://placehold.it/300x300" style="border-radius:100%;" width="172px">
|
<img class="rounded-circle card" src="{{$posts->last()->thumb()}}" width="172px" height="172px">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-9 d-flex align-items-center">
|
<div class="col-12 col-md-9 d-flex align-items-center">
|
||||||
|
@ -16,22 +16,36 @@
|
||||||
<span class="h1">{{$tag->name}}</span>
|
<span class="h1">{{$tag->name}}</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="font-weight-bold">
|
<p class="font-weight-bold">
|
||||||
{{$count}} posts
|
{{$tag->posts_count}} posts
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="profile-timeline mt-5 row">
|
<div class="tag-timeline row">
|
||||||
@foreach($posts as $status)
|
@foreach($posts as $status)
|
||||||
<div class="col-12 col-md-4 mb-4">
|
<div class="col-4 p-0 p-sm-2 p-md-3">
|
||||||
<a class="card" href="{{$status->url()}}">
|
<a class="card info-overlay card-md-border-0" href="{{$status->url()}}">
|
||||||
<img class="card-img-top" src="{{$status->thumb()}}" width="300px" height="300px">
|
<div class="square {{$status->firstMedia()->filter_class}}">
|
||||||
|
<div class="square-content" style="background-image: url('{{$status->thumb()}}')"></div>
|
||||||
|
<div class="info-overlay-text">
|
||||||
|
<h5 class="text-white m-auto font-weight-bold">
|
||||||
|
<span class="pr-4">
|
||||||
|
<span class="far fa-heart fa-lg pr-1"></span> {{$status->likes_count}}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<span class="far fa-comment fa-lg pr-1"></span> {{$status->comments_count}}
|
||||||
|
</span>
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
|
<div class="d-flex justify-content-center pagination-container mt-4">
|
||||||
|
{{$posts->links()}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@endsection
|
@endsection
|
||||||
|
@ -39,3 +53,21 @@
|
||||||
@push('meta')
|
@push('meta')
|
||||||
<meta property="og:description" content="Discover {{$tag->name}}">
|
<meta property="og:description" content="Discover {{$tag->name}}">
|
||||||
@endpush
|
@endpush
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('.pagination-container').hide();
|
||||||
|
$('.pagination').hide();
|
||||||
|
let elem = document.querySelector('.tag-timeline');
|
||||||
|
let infScroll = new InfiniteScroll( elem, {
|
||||||
|
path: '.pagination__next',
|
||||||
|
append: '.tag-timeline',
|
||||||
|
status: '.page-load-status',
|
||||||
|
history: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
|
|
|
@ -4,8 +4,9 @@
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="error-page py-5 my-5">
|
<div class="error-page py-5 my-5">
|
||||||
<div class="card mx-5">
|
<div class="card mx-5">
|
||||||
<div class="card-body p-5">
|
<div class="card-body p-5 text-center">
|
||||||
<h1 class="text-center">404 – Page Not Found</h1>
|
<h1 class="text-center">404 – Page Not Found</h1>
|
||||||
|
<img src="/img/fred1.gif" class="img-fluid">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,21 +7,20 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
|
|
||||||
<meta name="robots" content="noimageindex, noarchive">
|
|
||||||
<meta name="mobile-web-app-capable" content="yes">
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
|
|
||||||
<title>{{ $title or config('app.name', 'Laravel') }}</title>
|
<title>{{ $title ?? config('app.name', 'Laravel') }}</title>
|
||||||
<meta property="og:site_name" content="{{ config('app.name', 'Laravel') }}">
|
|
||||||
<meta property="og:title" content="{{ $title or config('app.name', 'Laravel') }}">
|
<meta property="og:site_name" content="{{ config('app.name', 'pixelfed') }}">
|
||||||
|
<meta property="og:title" content="{{ $title or config('app.name', 'pixelfed') }}">
|
||||||
<meta property="og:type" content="article">
|
<meta property="og:type" content="article">
|
||||||
<meta property="og:url" content="{{request()->url()}}">
|
<meta property="og:url" content="{{request()->url()}}">
|
||||||
|
|
||||||
@stack('meta')
|
@stack('meta')
|
||||||
|
|
||||||
<meta name="medium" content="image">
|
<meta name="medium" content="image">
|
||||||
<meta name="theme-color" content="#10c5f8">
|
<meta name="theme-color" content="#10c5f8">
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<link rel="shortcut icon" type="image/png" href="/img/favicon.png">
|
||||||
<link rel="canonical" href="{{request()->url()}}">
|
<link rel="canonical" href="{{request()->url()}}">
|
||||||
<link href="{{ mix('css/app.css') }}" rel="stylesheet">
|
<link href="{{ mix('css/app.css') }}" rel="stylesheet">
|
||||||
@stack('styles')
|
@stack('styles')
|
||||||
|
@ -34,5 +33,14 @@
|
||||||
@include('layouts.partial.footer')
|
@include('layouts.partial.footer')
|
||||||
<script type="text/javascript" src="{{ mix('js/app.js') }}"></script>
|
<script type="text/javascript" src="{{ mix('js/app.js') }}"></script>
|
||||||
@stack('scripts')
|
@stack('scripts')
|
||||||
|
@if(Auth::check())
|
||||||
|
<div class="modal" tabindex="-1" role="dialog" id="composeModal">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
@include('timeline.partial.new-form')
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<nav class="navbar navbar-expand navbar-light navbar-laravel sticky-top">
|
<nav class="navbar navbar-expand navbar-light navbar-laravel sticky-top">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<a class="navbar-brand d-flex align-items-center" href="{{ url('/timeline') }}" 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">
|
||||||
<span class="font-weight-bold mb-0" style="font-size:20px;">{{ config('app.name', 'Laravel') }}</span>
|
<span class="font-weight-bold mb-0 d-none d-sm-block" style="font-size:20px;">{{ config('app.name', 'Laravel') }}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||||
|
@ -16,19 +16,26 @@
|
||||||
|
|
||||||
<ul class="navbar-nav ml-auto">
|
<ul class="navbar-nav ml-auto">
|
||||||
@guest
|
@guest
|
||||||
<li><a class="nav-link font-weight-bold text-primary" href="{{ route('login') }}">{{ __('Login') }}</a></li>
|
<li><a class="nav-link font-weight-bold text-primary" href="{{ route('login') }}" title="Login">{{ __('Login') }}</a></li>
|
||||||
<li><a class="nav-link font-weight-bold" href="{{ route('register') }}">{{ __('Register') }}</a></li>
|
<li><a class="nav-link font-weight-bold" href="{{ route('register') }}" title="Register">{{ __('Register') }}</a></li>
|
||||||
@else
|
@else
|
||||||
<li class="nav-item px-2">
|
<li class="nav-item px-2">
|
||||||
<a class="nav-link" href="{{route('discover')}}" title="Discover"><i class="far fa-compass fa-lg"></i></a>
|
<a class="nav-link" href="{{route('discover')}}" title="Discover" data-toggle="tooltip" data-placement="bottom"><i class="far fa-compass fa-lg"></i></a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item px-2">
|
<li class="nav-item px-2">
|
||||||
<a class="nav-link" href="{{route('notifications')}}" title="Notifications">
|
<a class="nav-link nav-notification" href="{{route('notifications')}}" title="Notifications" data-toggle="tooltip" data-placement="bottom">
|
||||||
<i class="far fa-heart fa-lg"></i>
|
<i class="far fa-heart fa-lg text"></i>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item px-2">
|
||||||
|
<div title="Create new post" data-toggle="tooltip" data-placement="bottom">
|
||||||
|
<a href="{{route('compose')}}" class="nav-link" data-toggle="modal" data-target="#composeModal">
|
||||||
|
<i class="far fa-plus-square fa-lg text-primary"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
<li class="nav-item dropdown px-2">
|
<li class="nav-item dropdown px-2">
|
||||||
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre title="User Menu">
|
<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">
|
||||||
<i class="far fa-user fa-lg"></i> <span class="caret"></span>
|
<i class="far fa-user fa-lg"></i> <span class="caret"></span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
@ -47,6 +54,10 @@
|
||||||
<span class="far fa-list-alt pr-1"></span>
|
<span class="far fa-list-alt pr-1"></span>
|
||||||
{{__('navmenu.publicTimeline')}}
|
{{__('navmenu.publicTimeline')}}
|
||||||
</a>
|
</a>
|
||||||
|
{{-- <a class="dropdown-item font-weight-bold" href="{{route('messages')}}">
|
||||||
|
<span class="far fa-envelope pr-1"></span>
|
||||||
|
{{__('navmenu.directMessages')}}
|
||||||
|
</a> --}}
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
<a class="dropdown-item font-weight-bold" href="{{route('remotefollow')}}">
|
<a class="dropdown-item font-weight-bold" href="{{route('remotefollow')}}">
|
||||||
<span class="fas fa-user-plus pr-1"></span>
|
<span class="fas fa-user-plus pr-1"></span>
|
||||||
|
|
|
@ -62,4 +62,5 @@
|
||||||
@push('meta')
|
@push('meta')
|
||||||
<meta property="og:description" content="{{$profile->bio}}">
|
<meta property="og:description" content="{{$profile->bio}}">
|
||||||
<meta property="og:image" content="{{$profile->avatarUrl()}}">
|
<meta property="og:image" content="{{$profile->avatarUrl()}}">
|
||||||
|
<meta name="robots" content="NOINDEX, NOFOLLOW">
|
||||||
@endpush
|
@endpush
|
||||||
|
|
|
@ -62,4 +62,5 @@
|
||||||
@push('meta')
|
@push('meta')
|
||||||
<meta property="og:description" content="{{$profile->bio}}">
|
<meta property="og:description" content="{{$profile->bio}}">
|
||||||
<meta property="og:image" content="{{$profile->avatarUrl()}}">
|
<meta property="og:image" content="{{$profile->avatarUrl()}}">
|
||||||
|
<meta name="robots" content="NOINDEX, NOFOLLOW">
|
||||||
@endpush
|
@endpush
|
||||||
|
|
|
@ -16,18 +16,18 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
<div class="container mt-5">
|
<div class="container">
|
||||||
@if($owner && request()->is('*/saved'))
|
@if($owner && request()->is('*/saved'))
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<p class="text-muted font-weight-bold small">{{__('profile.savedWarning')}}</p>
|
<p class="text-muted font-weight-bold small">{{__('profile.savedWarning')}}</p>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
<div class="profile-timeline">
|
<div class="profile-timeline mt-2 mt-md-4">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@if($timeline->count() > 0)
|
@if($timeline->count() > 0)
|
||||||
@foreach($timeline as $status)
|
@foreach($timeline as $status)
|
||||||
<div class="col-12 col-md-4 mb-4">
|
<div class="col-4 p-0 p-sm-2 p-md-3">
|
||||||
<a class="card info-overlay" href="{{$status->url()}}">
|
<a class="card info-overlay card-md-border-0" href="{{$status->url()}}">
|
||||||
<div class="square {{$status->firstMedia()->filter_class}}">
|
<div class="square {{$status->firstMedia()->filter_class}}">
|
||||||
<div class="square-content" style="background-image: url('{{$status->thumb()}}')"></div>
|
<div class="square-content" style="background-image: url('{{$status->thumb()}}')"></div>
|
||||||
<div class="info-overlay-text">
|
<div class="info-overlay-text">
|
||||||
|
|
|
@ -6,8 +6,41 @@
|
||||||
<h3 class="font-weight-bold">Avatar Settings</h3>
|
<h3 class="font-weight-bold">Avatar Settings</h3>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<div class="alert alert-danger">
|
<div class="row mt-3">
|
||||||
Coming Soon
|
|
||||||
|
<div class="col-12 col-md-4">
|
||||||
|
<p class="font-weight-bold text-center">Current Avatar</p>
|
||||||
|
<img src="{{Auth::user()->profile->avatarUrl()}}" class="img-thumbnail rounded-circle">
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-7 offset-md-1">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header font-weight-bold bg-white">Update Avatar</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="post" enctype="multipart/form-data">
|
||||||
|
@csrf
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="custom-file">
|
||||||
|
<input type="file" class="custom-file-input" id="fileInput" name="avatar" accept="image/*">
|
||||||
|
<label class="custom-file-label" for="fileInput">Upload New Avatar</label>
|
||||||
|
</div>
|
||||||
|
<small class="form-text text-muted">
|
||||||
|
Max Size: 1 MB. Supported formats: jpeg, png.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 mt-5 pt-5">
|
||||||
|
<hr>
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="col-12 text-right">
|
||||||
|
{{-- <a class="btn btn-secondary font-weight-bold py-1" href="#">Restore Default Avatar</a> --}}
|
||||||
|
<button type="submit" class="btn btn-primary font-weight-bold py-1">Submit</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@endsection
|
@endsection
|
|
@ -8,6 +8,15 @@
|
||||||
<hr>
|
<hr>
|
||||||
<form method="post">
|
<form method="post">
|
||||||
@csrf
|
@csrf
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<img src="{{Auth::user()->profile->avatarUrl()}}" width="38px" class="rounded-circle img-thumbnail float-right">
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<p class="lead font-weight-bold mb-0">{{Auth::user()->username}}</p>
|
||||||
|
<p><a href="#" class="font-weight-bold change-profile-photo">Change Profile Photo</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="name" class="col-sm-3 col-form-label font-weight-bold text-right">Name</label>
|
<label for="name" class="col-sm-3 col-form-label font-weight-bold text-right">Name</label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
|
@ -15,15 +24,21 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="name" class="col-sm-3 col-form-label font-weight-bold text-right">Username</label>
|
<label for="username" class="col-sm-3 col-form-label font-weight-bold text-right">Username</label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input type="text" class="form-control" id="name" name="username" placeholder="Username" value="{{Auth::user()->profile->username}}" readonly>
|
<input type="text" class="form-control" id="username" name="username" placeholder="Username" value="{{Auth::user()->profile->username}}" readonly>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label class="col-sm-3 col-form-label font-weight-bold text-right">Bio</label>
|
<label for="website" class="col-sm-3 col-form-label font-weight-bold text-right">Website</label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<textarea class="form-control" name="bio" placeholder="Add a bio here" rows="2">{{Auth::user()->profile->bio}}</textarea>
|
<input type="text" class="form-control" id="website" name="website" placeholder="Website" value="{{Auth::user()->profile->website}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label for="bio" class="col-sm-3 col-form-label font-weight-bold text-right">Bio</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<textarea class="form-control" id="bio" name="bio" placeholder="Add a bio here" rows="2">{{Auth::user()->profile->bio}}</textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pt-5">
|
<div class="pt-5">
|
||||||
|
@ -32,15 +47,91 @@
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="email" class="col-sm-3 col-form-label font-weight-bold text-right">Email</label>
|
<label for="email" class="col-sm-3 col-form-label font-weight-bold text-right">Email</label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input type="email" class="form-control" id="email" name="email" placeholder="Email Address" value="{{Auth::user()->email}}" readonly>
|
<input type="email" class="form-control" id="email" name="email" placeholder="Email Address" value="{{Auth::user()->email}}">
|
||||||
|
<p class="help-text small text-muted font-weight-bold">
|
||||||
|
@if(Auth::user()->email_verified_at)
|
||||||
|
<span class="text-success">Verified</span> {{Auth::user()->email_verified_at->diffForHumans()}}
|
||||||
|
@else
|
||||||
|
<span class="text-danger">Unverified</span> You need to <a href="/i/verify-email">verify your email</a>.
|
||||||
|
@endif
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<div class="col-sm-9">
|
<div class="col-12 text-right">
|
||||||
<button type="submit" class="btn btn-primary">Submit</button>
|
<button type="submit" class="btn btn-primary font-weight-bold">Submit</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).on('click', '.modal-update', function(e) {
|
||||||
|
swal({
|
||||||
|
title: 'Upload Photo',
|
||||||
|
content: {
|
||||||
|
element: 'input',
|
||||||
|
attributes: {
|
||||||
|
placeholder: 'Upload your photo',
|
||||||
|
type: 'file',
|
||||||
|
name: 'photoUpload',
|
||||||
|
id: 'photoUploadInput'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buttons: {
|
||||||
|
confirm: {
|
||||||
|
text: 'Upload'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).then((res) => {
|
||||||
|
const input = $('#photoUploadInput')[0];
|
||||||
|
const photo = input.files[0];
|
||||||
|
const form = new FormData();
|
||||||
|
form.append("upload", photo);
|
||||||
|
|
||||||
|
axios.post('/api/v1/avatar/update', form, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data'
|
||||||
|
}
|
||||||
|
}).then((res) => {
|
||||||
|
swal('Success', 'Your photo has been successfully updated! It may take a few minutes to update across the site.', 'success');
|
||||||
|
}).catch((res) => {
|
||||||
|
let msg = res.response.data.errors.upload[0];
|
||||||
|
swal('Something went wrong', msg, 'error');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '.modal-close', function(e) {
|
||||||
|
swal.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '.change-profile-photo', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var content = $('<ul>').addClass('list-group');
|
||||||
|
var upload = $('<li>').text('Upload photo').addClass('list-group-item');
|
||||||
|
content.append(upload);
|
||||||
|
const list = document.createElement('ul');
|
||||||
|
list.className = 'list-group';
|
||||||
|
|
||||||
|
const uploadPhoto = document.createElement('li');
|
||||||
|
uploadPhoto.innerHTML = 'Upload Photo';
|
||||||
|
uploadPhoto.className = 'list-group-item font-weight-bold text-primary modal-update';
|
||||||
|
list.appendChild(uploadPhoto);
|
||||||
|
|
||||||
|
const cancel = document.createElement('li');
|
||||||
|
cancel.innerHTML = 'Cancel';
|
||||||
|
cancel.className = 'list-group-item modal-close';
|
||||||
|
list.appendChild(cancel);
|
||||||
|
|
||||||
|
swal({
|
||||||
|
title: 'Change Profile Photo',
|
||||||
|
content: list,
|
||||||
|
buttons: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endpush
|
|
@ -3,22 +3,23 @@
|
||||||
<li class="nav-item pl-3 {{request()->is('settings/home')?'active':''}}">
|
<li class="nav-item pl-3 {{request()->is('settings/home')?'active':''}}">
|
||||||
<a class="nav-link font-weight-light text-muted" href="{{route('settings')}}">Profile</a>
|
<a class="nav-link font-weight-light text-muted" href="{{route('settings')}}">Profile</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item pl-3 {{request()->is('settings/avatar')?'active':''}}">
|
{{-- <li class="nav-item pl-3 {{request()->is('settings/avatar')?'active':''}}">
|
||||||
<a class="nav-link font-weight-light text-muted" href="{{route('settings.avatar')}}">Avatar</a>
|
<a class="nav-link font-weight-light text-muted" href="{{route('settings.avatar')}}">Avatar</a>
|
||||||
</li>
|
</li> --}}
|
||||||
<li class="nav-item pl-3 {{request()->is('settings/password')?'active':''}}">
|
<li class="nav-item pl-3 {{request()->is('settings/password')?'active':''}}">
|
||||||
<a class="nav-link font-weight-light text-muted" href="{{route('settings.password')}}">Password</a>
|
<a class="nav-link font-weight-light text-muted" href="{{route('settings.password')}}">Password</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item pl-3 {{request()->is('settings/email')?'active':''}}">
|
{{-- <li class="nav-item pl-3 {{request()->is('settings/email')?'active':''}}">
|
||||||
<a class="nav-link font-weight-light text-muted" href="{{route('settings.email')}}">Email</a>
|
<a class="nav-link font-weight-light text-muted" href="{{route('settings.email')}}">Email</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item pl-3 {{request()->is('settings/notifications')?'active':''}}">
|
<li class="nav-item pl-3 {{request()->is('settings/notifications')?'active':''}}">
|
||||||
<a class="nav-link font-weight-light text-muted" href="{{route('settings.notifications')}}">Notifications</a>
|
<a class="nav-link font-weight-light text-muted" href="{{route('settings.notifications')}}">Notifications</a>
|
||||||
</li>
|
</li>
|
||||||
|
--}}
|
||||||
<li class="nav-item pl-3 {{request()->is('settings/privacy')?'active':''}}">
|
<li class="nav-item pl-3 {{request()->is('settings/privacy')?'active':''}}">
|
||||||
<a class="nav-link font-weight-light text-muted" href="{{route('settings.privacy')}}">Privacy</a>
|
<a class="nav-link font-weight-light text-muted" href="{{route('settings.privacy')}}">Privacy</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item pl-3 {{request()->is('settings/security')?'active':''}}">
|
{{-- <li class="nav-item pl-3 {{request()->is('settings/security')?'active':''}}">
|
||||||
<a class="nav-link font-weight-light text-muted" href="{{route('settings.security')}}">Security</a>
|
<a class="nav-link font-weight-light text-muted" href="{{route('settings.security')}}">Security</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
|
@ -39,6 +40,6 @@
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item pl-3 {{request()->is('settings/developers')?'active':''}}">
|
<li class="nav-item pl-3 {{request()->is('settings/developers')?'active':''}}">
|
||||||
<a class="nav-link font-weight-light text-muted" href="{{route('settings.developers')}}">Developers</a>
|
<a class="nav-link font-weight-light text-muted" href="{{route('settings.developers')}}">Developers</a>
|
||||||
</li>
|
</li> --}}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
|
@ -1,25 +1,27 @@
|
||||||
<div class="col-12 col-md-3 py-3" style="border-right:1px solid #ccc;">
|
<div class="col-12 col-md-3 py-3" style="border-right:1px solid #ccc;">
|
||||||
<ul class="nav flex-column settings-nav">
|
<ul class="nav flex-column settings-nav">
|
||||||
<li class="nav-item pl-3 {{request()->is('site/about')?'active':''}}">
|
<li class="nav-item pl-3 {{request()->is('site/about')?'active':''}}">
|
||||||
<a class="nav-link lead text-muted" href="{{route('site.about')}}">About</a>
|
<a class="nav-link font-weight-light text-muted" href="{{route('site.about')}}">About</a>
|
||||||
</li>
|
</li>
|
||||||
|
{{--
|
||||||
<li class="nav-item pl-3 {{request()->is('site/features')?'active':''}}">
|
<li class="nav-item pl-3 {{request()->is('site/features')?'active':''}}">
|
||||||
<a class="nav-link lead text-muted" href="{{route('site.features')}}">Features</a>
|
<a class="nav-link lead text-muted" href="{{route('site.features')}}">Features</a>
|
||||||
</li>
|
</li>
|
||||||
|
--}}
|
||||||
<li class="nav-item pl-3 {{request()->is('site/help')?'active':''}}">
|
<li class="nav-item pl-3 {{request()->is('site/help')?'active':''}}">
|
||||||
<a class="nav-link lead text-muted" href="{{route('site.help')}}">Help</a>
|
<a class="nav-link font-weight-light text-muted" href="{{route('site.help')}}">Help</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item pl-3 {{request()->is('site/language')?'active':''}}">
|
<li class="nav-item pl-3 {{request()->is('site/language')?'active':''}}">
|
||||||
<a class="nav-link lead text-muted" href="{{route('site.language')}}">Language</a>
|
<a class="nav-link font-weight-light text-muted" href="{{route('site.language')}}">Language</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<hr>
|
<hr>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item pl-3 {{request()->is('site/fediverse')?'active':''}}">
|
<li class="nav-item pl-3 {{request()->is('site/fediverse')?'active':''}}">
|
||||||
<a class="nav-link lead text-muted" href="{{route('site.fediverse')}}">Fediverse</a>
|
<a class="nav-link font-weight-light text-muted" href="{{route('site.fediverse')}}">Fediverse</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item pl-3 {{request()->is('site/open-source')?'active':''}}">
|
<li class="nav-item pl-3 {{request()->is('site/open-source')?'active':''}}">
|
||||||
<a class="nav-link lead text-muted" href="{{route('site.opensource')}}">Open Source</a>
|
<a class="nav-link font-weight-light text-muted" href="{{route('site.opensource')}}">Open Source</a>
|
||||||
</li>
|
</li>
|
||||||
{{-- <li class="nav-item pl-3 {{request()->is('site/banned-instances')?'active':''}}">
|
{{-- <li class="nav-item pl-3 {{request()->is('site/banned-instances')?'active':''}}">
|
||||||
<a class="nav-link lead text-muted" href="{{route('site.bannedinstances')}}">Banned Content</a>
|
<a class="nav-link lead text-muted" href="{{route('site.bannedinstances')}}">Banned Content</a>
|
||||||
|
@ -31,16 +33,18 @@
|
||||||
<hr>
|
<hr>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item pl-3 {{request()->is('site/terms')?'active':''}}">
|
<li class="nav-item pl-3 {{request()->is('site/terms')?'active':''}}">
|
||||||
<a class="nav-link lead text-muted" href="{{route('site.terms')}}">Terms</a>
|
<a class="nav-link font-weight-light text-muted" href="{{route('site.terms')}}">Terms</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item pl-3 {{request()->is('site/privacy')?'active':''}}">
|
<li class="nav-item pl-3 {{request()->is('site/privacy')?'active':''}}">
|
||||||
<a class="nav-link lead text-muted" href="{{route('site.privacy')}}">Privacy</a>
|
<a class="nav-link font-weight-light text-muted" href="{{route('site.privacy')}}">Privacy</a>
|
||||||
</li>
|
</li>
|
||||||
|
{{--
|
||||||
<li class="nav-item pl-3 {{request()->is('site/platform')?'active':''}}">
|
<li class="nav-item pl-3 {{request()->is('site/platform')?'active':''}}">
|
||||||
<a class="nav-link lead text-muted" href="{{route('site.platform')}}">Platform</a>
|
<a class="nav-link lead text-muted" href="{{route('site.platform')}}">Platform</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item pl-3 {{request()->is('site/libraries')?'active':''}}">
|
<li class="nav-item pl-3 {{request()->is('site/libraries')?'active':''}}">
|
||||||
<a class="nav-link lead text-muted" href="{{route('site.libraries')}}">Libraries</a>
|
<a class="nav-link lead text-muted" href="{{route('site.libraries')}}">Libraries</a>
|
||||||
</li>
|
</li>
|
||||||
|
--}}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
|
@ -66,13 +66,18 @@
|
||||||
<p class="lead">Your public content may be downloaded by other servers in the network. Your public and followers-only posts are delivered to the servers where your followers reside, and direct messages are delivered to the servers of the recipients, in so far as those followers or recipients reside on a different server than this.</p>
|
<p class="lead">Your public content may be downloaded by other servers in the network. Your public and followers-only posts are delivered to the servers where your followers reside, and direct messages are delivered to the servers of the recipients, in so far as those followers or recipients reside on a different server than this.</p>
|
||||||
<p class="lead">When you authorize an application to use your account, depending on the scope of permissions you approve, it may access your public profile information, your following list, your followers, your lists, all your posts, and your favourites. Applications can never access your e-mail address or password.</p>
|
<p class="lead">When you authorize an application to use your account, depending on the scope of permissions you approve, it may access your public profile information, your following list, your followers, your lists, all your posts, and your favourites. Applications can never access your e-mail address or password.</p>
|
||||||
|
|
||||||
<h4 class="font-weight-bold">Children’s Online Privacy Protection Act Compliance</h4>
|
<h4 class="font-weight-bold">Site usage by children</h4>
|
||||||
<p class="lead">Our site, products and services are all directed to people who are at least 13 years old. If this server is in the USA, and you are under the age of 13, per the requirements of COPPA (Children’s Online Privacy Protection Act) do not use this site.</p>
|
|
||||||
|
<p class="lead">If this server is in the EU or the EEA: Our site, products and services are all directed to people who are at least 16 years old. If you are under the age of 16, per the requirements of the GDPR (General Data Protection Regulation) do not use this site.</p>
|
||||||
|
|
||||||
|
<p class="lead">If this server is in the USA: Our site, products and services are all directed to people who are at least 13 years old. If you are under the age of 13, per the requirements of COPPA (Children's Online Privacy Protection Act) do not use this site.</p>
|
||||||
|
|
||||||
|
<p class="lead">Law requirements can be different if this server is in another jurisdiction.</p>
|
||||||
|
|
||||||
<h4 class="font-weight-bold">Changes to our Privacy Policy</h4>
|
<h4 class="font-weight-bold">Changes to our Privacy Policy</h4>
|
||||||
<p class="lead">If we decide to change our privacy policy, we will post those changes on this page.</p>
|
<p class="lead">If we decide to change our privacy policy, we will post those changes on this page.</p>
|
||||||
|
|
||||||
<p class="lead">This document is CC-BY-SA. It was last updated May 31, 2018.</p>
|
<p class="lead">This document is CC-BY-SA. It was last updated Jun 12, 2018.</p>
|
||||||
|
|
||||||
<p class="lead">Originally adapted from the <a href="https://mastodon.social/terms">Mastodon</a> privacy policy.</p>
|
<p class="lead">Originally adapted from the <a href="https://mastodon.social/terms">Mastodon</a> privacy policy.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
37
resources/views/status/comments.blade.php
Normal file
37
resources/views/status/comments.blade.php
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
|
||||||
|
<div class="container px-0 mt-md-4">
|
||||||
|
<div class="col-12 col-md-8 offset-md-2">
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="mb-0">
|
||||||
|
<img class="img-thumbnail mr-2" src="{{$user->avatarUrl()}}" width="24px" height="24px" style="border-radius:12px;">
|
||||||
|
<span class="font-weight-bold pr-1"><bdi><a class="text-dark" href="{{$status->profile->url()}}">{{ str_limit($status->profile->username, 15)}}</a></bdi></span>
|
||||||
|
<span class="comment-text">{!! $status->rendered ?? e($status->caption) !!} <a href="{{$status->url()}}" class="text-dark small font-weight-bold float-right pl-2">{{$status->created_at->diffForHumans(null, true, true ,true)}}</a></span>
|
||||||
|
</p>
|
||||||
|
<hr>
|
||||||
|
<div class="comments">
|
||||||
|
@foreach($replies as $item)
|
||||||
|
<p class="mb-2">
|
||||||
|
<span class="font-weight-bold pr-1">
|
||||||
|
<img class="img-thumbnail mr-2" src="{{$item->profile->avatarUrl()}}" width="24px" height="24px" style="border-radius:12px;">
|
||||||
|
<bdi><a class="text-dark" href="{{$item->profile->url()}}">{{ str_limit($item->profile->username, 15)}}</a></bdi>
|
||||||
|
</span>
|
||||||
|
<span class="comment-text">
|
||||||
|
{!! $item->rendered ?? e($item->caption) !!}
|
||||||
|
<a href="{{$item->url()}}" class="text-dark small font-weight-bold float-right pl-2">
|
||||||
|
{{$item->created_at->diffForHumans(null, true, true ,true)}}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@endsection
|
|
@ -18,12 +18,13 @@
|
||||||
<div class="col-12 col-md-8 status-photo px-0">
|
<div class="col-12 col-md-8 status-photo px-0">
|
||||||
@if($status->is_nsfw && $status->media_count == 1)
|
@if($status->is_nsfw && $status->media_count == 1)
|
||||||
<details class="details-animated">
|
<details class="details-animated">
|
||||||
<p>
|
<summary>
|
||||||
<summary>NSFW / Hidden Image</summary>
|
<p class="mb-0 lead font-weight-bold">CW / NSFW / Hidden Media</p>
|
||||||
|
<p class="font-weight-light">(click to show)</p>
|
||||||
|
</summary>
|
||||||
<a class="max-hide-overflow {{$status->firstMedia()->filter_class}}" href="{{$status->url()}}">
|
<a class="max-hide-overflow {{$status->firstMedia()->filter_class}}" href="{{$status->url()}}">
|
||||||
<img class="card-img-top" src="{{$status->mediaUrl()}}">
|
<img class="card-img-top" src="{{$status->mediaUrl()}}">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
|
||||||
</details>
|
</details>
|
||||||
@elseif(!$status->is_nsfw && $status->media_count == 1)
|
@elseif(!$status->is_nsfw && $status->media_count == 1)
|
||||||
<div class="{{$status->firstMedia()->filter_class}}">
|
<div class="{{$status->firstMedia()->filter_class}}">
|
||||||
|
@ -68,19 +69,43 @@
|
||||||
<a href="{{$user->url()}}" class="username-link font-weight-bold text-dark">{{$user->username}}</a>
|
<a href="{{$user->url()}}" class="username-link font-weight-bold text-dark">{{$user->username}}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="float-right">
|
||||||
|
<div class="dropdown">
|
||||||
|
<button class="btn btn-link text-dark no-caret dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" title="Post options">
|
||||||
|
<span class="fas fa-ellipsis-v text-muted"></span>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
|
||||||
|
<a class="dropdown-item font-weight-bold" href="{{$status->reportUrl()}}">Report</a>
|
||||||
|
{{-- <a class="dropdown-item" href="#">Embed</a> --}}
|
||||||
|
@if(Auth::check())
|
||||||
|
@if(Auth::user()->profile->id === $status->profile->id || Auth::user()->is_admin == true)
|
||||||
|
{{-- <a class="dropdown-item" href="{{$status->editUrl()}}">Edit</a> --}}
|
||||||
|
<form method="post" action="/i/delete">
|
||||||
|
@csrf
|
||||||
|
<input type="hidden" name="type" value="post">
|
||||||
|
<input type="hidden" name="item" value="{{$status->id}}">
|
||||||
|
<button type="submit" class="dropdown-item btn btn-link font-weight-bold">Delete</button>
|
||||||
|
</form>
|
||||||
|
@endif
|
||||||
|
@endif
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-md-column flex-column-reverse h-100">
|
<div class="d-flex flex-md-column flex-column-reverse h-100">
|
||||||
<div class="card-body status-comments">
|
<div class="card-body status-comments">
|
||||||
<div class="status-comment">
|
<div class="status-comment">
|
||||||
<p class="mb-1">
|
<p class="mb-1">
|
||||||
<span class="font-weight-bold pr-1">{{$status->profile->username}}</span>
|
<span class="font-weight-bold pr-1">{{$status->profile->username}}</span>
|
||||||
<span class="comment-text">{!! $status->rendered ?? e($status->caption) !!}</span>
|
<span class="comment-text" v-pre>{!! $status->rendered ?? e($status->caption) !!}</span>
|
||||||
</p>
|
</p>
|
||||||
|
<p class="mb-1"><a href="{{$status->url()}}/c" class="text-muted">View all comments</a></p>
|
||||||
<div class="comments">
|
<div class="comments">
|
||||||
@foreach($status->comments->reverse()->take(10) as $item)
|
@foreach($replies as $item)
|
||||||
<p class="mb-0">
|
<p class="mb-1">
|
||||||
<span class="font-weight-bold pr-1"><bdi><a class="text-dark" href="{{$item->profile->url()}}">{{$item->profile->username}}</a></bdi></span>
|
<span class="font-weight-bold pr-1"><bdi><a class="text-dark" href="{{$item->profile->url()}}">{{ str_limit($item->profile->username, 15)}}</a></bdi></span>
|
||||||
<span class="comment-text">{!! $item->rendered ?? e($item->caption) !!} <a href="{{$item->url()}}" class="text-dark small font-weight-bold float-right">{{$item->created_at->diffForHumans(null, true, true ,true)}}</a></span>
|
<span class="comment-text" v-pre>{!! $item->rendered ?? e($item->caption) !!} <a href="{{$item->url()}}" class="text-dark small font-weight-bold float-right pl-2">{{$item->created_at->diffForHumans(null, true, true ,true)}}</a></span>
|
||||||
</p>
|
</p>
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
|
@ -88,32 +113,30 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body flex-grow-0 py-1">
|
<div class="card-body flex-grow-0 py-1">
|
||||||
<div class="reactions my-1">
|
<div class="reactions my-1">
|
||||||
<form class="d-inline-flex like-form pr-3" method="post" action="/i/like" style="display: inline;" data-id="{{$status->id}}" data-action="like">
|
@if(Auth::check())
|
||||||
|
<form class="d-inline-flex pr-3" method="post" action="/i/like" style="display: inline;" data-id="{{$status->id}}" data-action="like">
|
||||||
@csrf
|
@csrf
|
||||||
<input type="hidden" name="item" value="{{$status->id}}">
|
<input type="hidden" name="item" value="{{$status->id}}">
|
||||||
<button class="btn btn-link text-dark p-0 border-0" type="submit" title="Like!">
|
<button class="btn btn-link text-dark p-0 border-0" type="submit" title="Like!">
|
||||||
<h3 class="far fa-heart m-0"></h3>
|
<h3 class="m-0 {{$status->liked() ? 'fas fa-heart text-danger':'far fa-heart text-dark'}}"></h3>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<h3 class="far fa-comment pr-3 m-0" title="Comment"></h3>
|
<h3 class="far fa-comment pr-3 m-0" title="Comment"></h3>
|
||||||
@if(Auth::check())
|
<form class="d-inline-flex share-form pr-3" method="post" action="/i/share" style="display: inline;" data-id="{{$status->id}}" data-action="share" data-count="{{$status->shares_count}}">
|
||||||
@if(Auth::user()->profile->id === $status->profile->id || Auth::user()->is_admin == true)
|
|
||||||
<form method="post" action="/i/delete" class="d-inline-flex">
|
|
||||||
@csrf
|
@csrf
|
||||||
<input type="hidden" name="type" value="post">
|
|
||||||
<input type="hidden" name="item" value="{{$status->id}}">
|
<input type="hidden" name="item" value="{{$status->id}}">
|
||||||
<button type="submit" class="btn btn-link text-dark p-0 border-0" title="Remove">
|
<button class="btn btn-link text-dark p-0" type="submit" title="Share">
|
||||||
<h3 class="far fa-trash-alt m-0"></h3>
|
<h3 class="m-0 {{$status->shared() ? 'fas fa-share-square text-primary':'far fa-share-square '}}"></h3>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@endif
|
|
||||||
@endif
|
@endif
|
||||||
<span class="float-right">
|
<span class="float-right">
|
||||||
<form class="d-inline-flex bookmark-form" method="post" action="/i/bookmark" style="display: inline;" data-id="{{$status->id}}" data-action="bookmark">
|
<form class="d-inline-flex " method="post" action="/i/bookmark" style="display: inline;" data-id="{{$status->id}}" data-action="bookmark">
|
||||||
@csrf
|
@csrf
|
||||||
<input type="hidden" name="item" value="{{$status->id}}">
|
<input type="hidden" name="item" value="{{$status->id}}">
|
||||||
<button class="btn btn-link text-dark p-0 border-0" type="submit" title="Save">
|
<button class="btn btn-link text-dark p-0 border-0" type="submit" title="Save">
|
||||||
<h3 class="far fa-bookmark m-0"></h3>
|
<h3 class="m-0 {{$status->bookmarked() ? 'fas fa-bookmark text-warning':'far fa-bookmark'}}"></h3>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</span>
|
</span>
|
||||||
|
@ -132,7 +155,8 @@
|
||||||
<form class="comment-form" method="post" action="/i/comment" data-id="{{$status->id}}" data-truncate="false">
|
<form class="comment-form" method="post" action="/i/comment" data-id="{{$status->id}}" data-truncate="false">
|
||||||
@csrf
|
@csrf
|
||||||
<input type="hidden" name="item" value="{{$status->id}}">
|
<input type="hidden" name="item" value="{{$status->id}}">
|
||||||
<input class="form-control" name="comment" placeholder="Add a comment...">
|
|
||||||
|
<input class="form-control" name="comment" placeholder="Add a comment..." autocomplete="off">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -144,5 +168,5 @@
|
||||||
|
|
||||||
@push('meta')
|
@push('meta')
|
||||||
<meta property="og:description" content="{{ $status->caption }}">
|
<meta property="og:description" content="{{ $status->caption }}">
|
||||||
<meta property="og:image" content="{{$status->mediaUrl()}}">
|
<meta property="og:image" content="{{$status->mediaUrl()}}">
|
||||||
@endpush
|
@endpush
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<div class="card my-4 status-card card-md-rounded-0">
|
<div class="card mb-4 status-card card-md-rounded-0" data-id="{{$item->id}}" data-comment-max-id="0">
|
||||||
<div class="card-header d-inline-flex align-items-center bg-white">
|
<div class="card-header d-inline-flex align-items-center bg-white">
|
||||||
<img src="{{$item->profile->avatarUrl()}}" width="32px" height="32px" style="border-radius: 32px;">
|
<img src="{{$item->profile->avatarUrl()}}" width="32px" height="32px" style="border-radius: 32px;">
|
||||||
<a class="username font-weight-bold pl-2 text-dark" href="{{$item->profile->url()}}">
|
<a class="username font-weight-bold pl-2 text-dark" href="{{$item->profile->url()}}">
|
||||||
|
@ -10,17 +10,17 @@
|
||||||
<span class="fas fa-ellipsis-v fa-lg text-muted"></span>
|
<span class="fas fa-ellipsis-v fa-lg text-muted"></span>
|
||||||
</button>
|
</button>
|
||||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
|
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
|
||||||
<a class="dropdown-item" href="{{$item->url()}}">Go to post</a>
|
<a class="dropdown-item font-weight-bold" href="{{$item->url()}}">Go to post</a>
|
||||||
<a class="dropdown-item" href="{{route('report.form')}}?type=post&id={{$item->id}}">Report Inappropriate</a>
|
<a class="dropdown-item font-weight-bold" href="{{route('report.form')}}?type=post&id={{$item->id}}">Report</a>
|
||||||
<a class="dropdown-item" href="#">Embed</a>
|
<a class="dropdown-item font-weight-bold" href="#">Embed</a>
|
||||||
@if(Auth::check())
|
@if(Auth::check())
|
||||||
@if(Auth::user()->profile->id === $item->profile->id || Auth::user()->is_admin == true)
|
@if(Auth::user()->profile->id === $item->profile->id || Auth::user()->is_admin == true)
|
||||||
<a class="dropdown-item" href="{{$item->editUrl()}}">Edit</a>
|
<a class="dropdown-item font-weight-bold" href="{{$item->editUrl()}}">Edit</a>
|
||||||
<form method="post" action="/i/delete">
|
<form method="post" action="/i/delete">
|
||||||
@csrf
|
@csrf
|
||||||
<input type="hidden" name="type" value="post">
|
<input type="hidden" name="type" value="post">
|
||||||
<input type="hidden" name="item" value="{{$item->id}}">
|
<input type="hidden" name="item" value="{{$item->id}}">
|
||||||
<button type="submit" class="dropdown-item btn btn-link">Delete</button>
|
<button type="submit" class="dropdown-item btn btn-link text-danger font-weight-bold">Delete</button>
|
||||||
</form>
|
</form>
|
||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
|
@ -31,16 +31,21 @@
|
||||||
</div>
|
</div>
|
||||||
@if($item->is_nsfw)
|
@if($item->is_nsfw)
|
||||||
<details class="details-animated">
|
<details class="details-animated">
|
||||||
<p>
|
<summary>
|
||||||
<summary>NSFW / Hidden Image</summary>
|
<p class="mb-0 px-3 lead font-weight-bold">Content Warning: This may contain potentially sensitive content.</p>
|
||||||
|
<p class="font-weight-light">(click to show)</p>
|
||||||
|
</summary>
|
||||||
<a class="max-hide-overflow {{$item->firstMedia()->filter_class}}" href="{{$item->url()}}">
|
<a class="max-hide-overflow {{$item->firstMedia()->filter_class}}" href="{{$item->url()}}">
|
||||||
<img class="card-img-top" src="{{$item->mediaUrl()}}">
|
<img class="card-img-top lazy" src="data:image/gif;base64,R0lGODlhAQABAIAAAMLCwgAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" data-src="{{$item->mediaUrl()}}" data-srcset="{{$item->mediaUrl()}} 1x">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
|
||||||
</details>
|
</details>
|
||||||
@else
|
@else
|
||||||
<a class="max-hide-overflow {{$item->firstMedia()->filter_class}}" href="{{$item->url()}}">
|
<a class="max-hide-overflow {{$item->firstMedia()->filter_class}}" href="{{$item->url()}}">
|
||||||
<img class="card-img-top" src="{{$item->mediaUrl()}}">
|
@if($loop->index < 2)
|
||||||
|
<img class="card-img-top" src="{{$item->mediaUrl()}}" data-srcset="{{$item->mediaUrl()}} 1x">
|
||||||
|
@else
|
||||||
|
<img class="card-img-top lazy" src="data:image/gif;base64,R0lGODlhAQABAIAAAMLCwgAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" data-src="{{$item->mediaUrl()}}" data-srcset="{{$item->mediaUrl()}} 1x">
|
||||||
|
@endif
|
||||||
</a>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
@ -48,11 +53,18 @@
|
||||||
<form class="d-inline-flex like-form pr-3" method="post" action="/i/like" style="display: inline;" data-id="{{$item->id}}" data-action="like" data-count="{{$item->likes_count}}">
|
<form class="d-inline-flex like-form pr-3" method="post" action="/i/like" style="display: inline;" data-id="{{$item->id}}" data-action="like" data-count="{{$item->likes_count}}">
|
||||||
@csrf
|
@csrf
|
||||||
<input type="hidden" name="item" value="{{$item->id}}">
|
<input type="hidden" name="item" value="{{$item->id}}">
|
||||||
<button class="btn btn-link text-dark p-0" type="submit" title=""Like!>
|
<button class="btn btn-link text-dark p-0" type="submit" title="Like!">
|
||||||
<h3 class="far fa-heart status-heart m-0"></h3>
|
<h3 class="far fa-heart status-heart m-0"></h3>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<h3 class="far fa-comment status-comment-focus" title="Comment"></h3>
|
<h3 class="far fa-comment pr-3 status-comment-focus" title="Comment"></h3>
|
||||||
|
<form class="d-inline-flex share-form pr-3" method="post" action="/i/share" style="display: inline;" data-id="{{$item->id}}" data-action="share" data-count="{{$item->shares_count}}">
|
||||||
|
@csrf
|
||||||
|
<input type="hidden" name="item" value="{{$item->id}}">
|
||||||
|
<button class="btn btn-link text-dark p-0" type="submit" title="Share">
|
||||||
|
<h3 class="far fa-share-square m-0"></h3>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
<span class="float-right">
|
<span class="float-right">
|
||||||
<form class="d-inline-flex bookmark-form" method="post" action="/i/bookmark" style="display: inline;" data-id="{{$item->id}}" data-action="bookmark">
|
<form class="d-inline-flex bookmark-form" method="post" action="/i/bookmark" style="display: inline;" data-id="{{$item->id}}" data-action="bookmark">
|
||||||
@csrf
|
@csrf
|
||||||
|
@ -74,28 +86,7 @@
|
||||||
<span>{!! $item->rendered ?? e($item->caption) !!}</span>
|
<span>{!! $item->rendered ?? e($item->caption) !!}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@if($item->comments()->count() > 3)
|
|
||||||
<div class="more-comments">
|
|
||||||
<a class="text-muted" href="{{$item->url()}}">Load more comments</a>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
<div class="comments">
|
<div class="comments">
|
||||||
@if(isset($showSingleComment) && $showSingleComment === true)
|
|
||||||
<p class="mb-0">
|
|
||||||
<span class="font-weight-bold pr-1">
|
|
||||||
<bdi>
|
|
||||||
<a class="text-dark" href="{{$status->profile->url()}}">{{$status->profile->username}}</a>
|
|
||||||
</bdi>
|
|
||||||
</span>
|
|
||||||
<span class="comment-text">{!! $item->rendered ?? e($item->caption) !!}</span>
|
|
||||||
<span class="float-right">
|
|
||||||
<a href="{{$status->url()}}" class="text-dark small font-weight-bold">
|
|
||||||
{{$status->created_at->diffForHumans(null, true, true, true)}}
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
@else
|
|
||||||
@endif
|
|
||||||
</div>
|
</div>
|
||||||
<div class="timestamp pt-1">
|
<div class="timestamp pt-1">
|
||||||
<p class="small text-uppercase mb-0"><a href="{{$item->url()}}" class="text-muted">{{$item->created_at->diffForHumans()}}</a></p>
|
<p class="small text-uppercase mb-0"><a href="{{$item->url()}}" class="text-muted">{{$item->created_at->diffForHumans()}}</a></p>
|
||||||
|
@ -105,7 +96,7 @@
|
||||||
<form class="comment-form" method="post" action="/i/comment" data-id="{{$item->id}}" data-truncate="true">
|
<form class="comment-form" method="post" action="/i/comment" data-id="{{$item->id}}" data-truncate="true">
|
||||||
@csrf
|
@csrf
|
||||||
<input type="hidden" name="item" value="{{$item->id}}">
|
<input type="hidden" name="item" value="{{$item->id}}">
|
||||||
<input class="form-control status-reply-input" name="comment" placeholder="Add a comment…">
|
<input class="form-control status-reply-input" name="comment" placeholder="Add a comment…" autocomplete="off">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,35 +1,35 @@
|
||||||
<div class="card card-md-rounded-0">
|
<div class="card card-md-rounded-0">
|
||||||
<div class="card-header font-weight-bold">New Post</div>
|
<div class="card-header bg-white font-weight-bold">
|
||||||
<div class="card-body" id="statusForm">
|
<div>{{__('Create New Post')}}</div>
|
||||||
@if (session('error'))
|
|
||||||
<div class="alert alert-danger">
|
|
||||||
{{ session('error') }}
|
|
||||||
</div>
|
</div>
|
||||||
@endif
|
<div class="card-body" id="statusForm">
|
||||||
<form method="post" action="/timeline" enctype="multipart/form-data">
|
|
||||||
|
<form method="post" action="{{route('timeline.personal')}}" enctype="multipart/form-data">
|
||||||
@csrf
|
@csrf
|
||||||
<input type="hidden" name="filter_name" value="">
|
<input type="hidden" name="filter_name" value="">
|
||||||
<input type="hidden" name="filter_class" value="">
|
<input type="hidden" name="filter_class" value="">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="font-weight-bold text-muted small">Upload Image</label>
|
<div class="custom-file">
|
||||||
<input type="file" class="form-control-file" id="fileInput" name="photo[]" accept="image/*" multiple="">
|
<input type="file" class="custom-file-input" id="fileInput" name="photo[]" accept="image/*" multiple="">
|
||||||
|
<label class="custom-file-label" for="fileInput">Upload Image(s)</label>
|
||||||
|
</div>
|
||||||
<small class="form-text text-muted">
|
<small class="form-text text-muted">
|
||||||
Max Size: @maxFileSize(). Supported formats: jpeg, png, gif, bmp. Limited to {{config('pixelfed.max_album_length')}} photos per post.
|
Max Size: @maxFileSize(). Supported formats: jpeg, png, gif, bmp. Limited to {{config('pixelfed.max_album_length')}} photos per post.
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="font-weight-bold text-muted small">Caption</label>
|
<textarea class="form-control" name="caption" placeholder="Add a caption here" autocomplete="off" data-limit="{{config('pixelfed.max_caption_length')}}" rows="1"></textarea>
|
||||||
<input type="text" class="form-control" name="caption" placeholder="Add a caption here" autocomplete="off">
|
<p class="form-text text-muted small text-right">
|
||||||
<small class="form-text text-muted">
|
<span class="caption-counter">0</span>
|
||||||
Max length: {{config('pixelfed.max_caption_length')}} characters.
|
<span>/</span>
|
||||||
</small>
|
<span>{{config('pixelfed.max_caption_length')}}</span>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<button class="btn btn-primary btn-sm px-3 py-1 font-weight-bold" type="button" data-toggle="collapse" data-target="#collapsePreview" aria-expanded="false" aria-controls="collapsePreview">
|
<button class="btn btn-outline-primary btn-sm px-3 py-1 font-weight-bold" type="button" data-toggle="collapse" data-target="#collapsePreview" aria-expanded="false" aria-controls="collapsePreview">
|
||||||
Options
|
Options <i class="fas fa-chevron-down"></i>
|
||||||
</button>
|
</button>
|
||||||
<div class="collapse" id="collapsePreview">
|
<div class="collapse" id="collapsePreview">
|
||||||
|
|
||||||
<div class="form-group pt-3">
|
<div class="form-group pt-3">
|
||||||
<label class="font-weight-bold text-muted small">CW/NSFW</label>
|
<label class="font-weight-bold text-muted small">CW/NSFW</label>
|
||||||
<div class="switch switch-sm">
|
<div class="switch switch-sm">
|
||||||
|
@ -41,17 +41,6 @@
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- <div class="form-group">
|
|
||||||
<label class="font-weight-bold text-muted small">Visibility</label>
|
|
||||||
<div class="switch switch-sm">
|
|
||||||
<input type="checkbox" class="switch" id="visibility-switch" name="visibility">
|
|
||||||
<label for="visibility-switch" class="small font-weight-bold">Public | Followers-only</label>
|
|
||||||
</div>
|
|
||||||
<small class="form-text text-muted">
|
|
||||||
Toggle this to limit this post to your followers only.
|
|
||||||
</small>
|
|
||||||
</div> --}}
|
|
||||||
|
|
||||||
<div class="form-group d-none form-preview">
|
<div class="form-group d-none form-preview">
|
||||||
<label class="font-weight-bold text-muted small">Photo Preview</label>
|
<label class="font-weight-bold text-muted small">Photo Preview</label>
|
||||||
<figure class="filterContainer">
|
<figure class="filterContainer">
|
||||||
|
@ -69,7 +58,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-outline-primary btn-block">Post</button>
|
<button type="submit" class="btn btn-outline-primary btn-block font-weight-bold">Create Post</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
115
resources/views/timeline/template.blade.php
Normal file
115
resources/views/timeline/template.blade.php
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
|
||||||
|
<noscript>
|
||||||
|
<div class="container">
|
||||||
|
<div class="card border-left-primary mt-5">
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="mb-0 font-weight-bold">Javascript is required for an optimized experience, please enable it or use the <a href="#">lite</a> version.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</noscript>
|
||||||
|
|
||||||
|
<div class="container p-0 d-none timeline-container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-10 col-lg-8 mx-auto pt-4 px-0 my-3 pr-2">
|
||||||
|
@if (session('status'))
|
||||||
|
<div class="alert alert-success">
|
||||||
|
<span class="font-weight-bold">{!! session('status') !!}</span>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@if (session('error'))
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<span class="font-weight-bold">{!! session('error') !!}</span>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
|
||||||
|
<div class="timeline-feed" data-timeline="{{$type}}">
|
||||||
|
|
||||||
|
@foreach($timeline as $item)
|
||||||
|
@if(is_null($item->in_reply_to_id))
|
||||||
|
@include('status.template')
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
@if($timeline->count() == 0)
|
||||||
|
<div class="card card-md-rounded-0">
|
||||||
|
<div class="card-body py-5">
|
||||||
|
<div class="d-flex justify-content-center align-items-center">
|
||||||
|
<p class="lead font-weight-bold mb-0">{{ __('timeline.emptyPersonalTimeline') }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page-load-status" style="display: none;">
|
||||||
|
<div class="infinite-scroll-request" style="display: none;">
|
||||||
|
<div class="fixed-top loading-page"></div>
|
||||||
|
</div>
|
||||||
|
<div class="infinite-scroll-last" style="display: none;">
|
||||||
|
<h3>No more content</h3>
|
||||||
|
<p class="text-muted">
|
||||||
|
Maybe you could try
|
||||||
|
<a href="{{route('discover')}}">discovering</a>
|
||||||
|
more people you can follow.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="infinite-scroll-error" style="display: none;">
|
||||||
|
<h3>Whoops, an error</h3>
|
||||||
|
<p class="text-muted">
|
||||||
|
Try reloading the page
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
{{$timeline->links()}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 col-lg-4 pt-4 my-3">
|
||||||
|
|
||||||
|
<div class="media d-flex align-items-center mb-4">
|
||||||
|
<a href="{{Auth::user()->profile->url()}}">
|
||||||
|
<img class="mr-3 rounded-circle box-shadow" src="{{Auth::user()->profile->avatarUrl()}}" alt="{{Auth::user()->username}}'s avatar" width="64px">
|
||||||
|
</a>
|
||||||
|
<div class="media-body">
|
||||||
|
<p class="mb-0 px-0 font-weight-bold"><a href="{{Auth::user()->profile->url()}}">@{{Auth::user()->username}}</a></p>
|
||||||
|
<p class="mb-0 small text-muted">{{Auth::user()->name}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<follow-suggestions></follow-suggestions>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<div class="container pb-5">
|
||||||
|
<p class="mb-0 text-uppercase font-weight-bold text-muted small">
|
||||||
|
<a href="{{route('site.about')}}" class="text-dark pr-2">About Us</a>
|
||||||
|
<a href="{{route('site.help')}}" class="text-dark pr-2">Support</a>
|
||||||
|
<a href="{{route('site.opensource')}}" class="text-dark pr-2">Open Source</a>
|
||||||
|
<a href="{{route('site.language')}}" class="text-dark pr-2">Language</a>
|
||||||
|
<a href="{{route('site.terms')}}" class="text-dark pr-2">Terms</a>
|
||||||
|
<a href="{{route('site.privacy')}}" class="text-dark pr-2">Privacy</a>
|
||||||
|
<a href="{{route('site.platform')}}" class="text-dark pr-2">API</a>
|
||||||
|
<a href="#" class="text-dark pr-2">Directory</a>
|
||||||
|
<a href="#" class="text-dark pr-2">Profiles</a>
|
||||||
|
<a href="#" class="text-dark">Hashtags</a>
|
||||||
|
</p>
|
||||||
|
<p class="mb-0 text-uppercase font-weight-bold text-muted small">
|
||||||
|
<a href="http://pixelfed.org" class="text-muted" rel="noopener">Powered by PixelFed</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script type="text/javascript" src="{{mix('js/timeline.js')}}"></script>
|
||||||
|
@endpush
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
Route::domain(config('pixelfed.domain.admin'))->group(function() {
|
Route::domain(config('pixelfed.domain.admin'))->prefix('i/admin')->group(function() {
|
||||||
Route::redirect('/', '/dashboard');
|
Route::redirect('/', '/dashboard');
|
||||||
Route::redirect('timeline', config('app.url').'/timeline');
|
Route::redirect('timeline', config('app.url').'/timeline');
|
||||||
Route::get('dashboard', 'AdminController@home')->name('admin.home');
|
Route::get('dashboard', 'AdminController@home')->name('admin.home');
|
||||||
|
@ -15,7 +15,8 @@ Route::domain(config('pixelfed.domain.admin'))->group(function() {
|
||||||
|
|
||||||
Route::domain(config('pixelfed.domain.app'))->middleware('validemail')->group(function() {
|
Route::domain(config('pixelfed.domain.app'))->middleware('validemail')->group(function() {
|
||||||
|
|
||||||
Route::view('/', 'welcome');
|
Route::get('/', 'SiteController@home')->name('timeline.personal');
|
||||||
|
Route::post('/', 'StatusController@store');
|
||||||
|
|
||||||
Auth::routes();
|
Auth::routes();
|
||||||
|
|
||||||
|
@ -35,18 +36,28 @@ Route::domain(config('pixelfed.domain.app'))->middleware('validemail')->group(fu
|
||||||
Route::get('search/{tag}', 'SearchController@searchAPI')
|
Route::get('search/{tag}', 'SearchController@searchAPI')
|
||||||
->where('tag', '[A-Za-z0-9]+');
|
->where('tag', '[A-Za-z0-9]+');
|
||||||
Route::get('nodeinfo/2.0.json', 'FederationController@nodeinfo');
|
Route::get('nodeinfo/2.0.json', 'FederationController@nodeinfo');
|
||||||
Route::get('v1/likes', 'ApiController@hydrateLikes');
|
|
||||||
|
Route::group(['prefix' => 'v1'], function() {
|
||||||
|
Route::post('avatar/update', 'ApiController@avatarUpdate');
|
||||||
|
Route::get('likes', 'ApiController@hydrateLikes');
|
||||||
|
});
|
||||||
|
Route::group(['prefix' => 'local'], function() {
|
||||||
|
Route::get('i/follow-suggestions', 'ApiController@followSuggestions');
|
||||||
|
Route::post('i/more-comments', 'ApiController@loadMoreComments');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::get('discover/tags/{hashtag}', 'DiscoverController@showTags');
|
Route::get('discover/tags/{hashtag}', 'DiscoverController@showTags');
|
||||||
|
|
||||||
Route::group(['prefix' => 'i'], function() {
|
Route::group(['prefix' => 'i'], function() {
|
||||||
Route::redirect('/', '/');
|
Route::redirect('/', '/');
|
||||||
|
Route::get('compose', 'StatusController@compose')->name('compose');
|
||||||
Route::get('remote-follow', 'FederationController@remoteFollow')->name('remotefollow');
|
Route::get('remote-follow', 'FederationController@remoteFollow')->name('remotefollow');
|
||||||
Route::post('remote-follow', 'FederationController@remoteFollowStore');
|
Route::post('remote-follow', 'FederationController@remoteFollowStore');
|
||||||
Route::post('comment', 'CommentController@store');
|
Route::post('comment', 'CommentController@store');
|
||||||
Route::post('delete', 'StatusController@delete');
|
Route::post('delete', 'StatusController@delete');
|
||||||
Route::post('like', 'LikeController@store');
|
Route::post('like', 'LikeController@store');
|
||||||
|
Route::post('share', 'StatusController@storeShare');
|
||||||
Route::post('follow', 'FollowerController@store');
|
Route::post('follow', 'FollowerController@store');
|
||||||
Route::post('bookmark', 'BookmarkController@store');
|
Route::post('bookmark', 'BookmarkController@store');
|
||||||
Route::get('lang/{locale}', 'SiteController@changeLocale');
|
Route::get('lang/{locale}', 'SiteController@changeLocale');
|
||||||
|
@ -62,6 +73,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware('validemail')->group(fu
|
||||||
Route::get('spam/post', 'ReportController@spamPostForm')->name('report.spam.post');
|
Route::get('spam/post', 'ReportController@spamPostForm')->name('report.spam.post');
|
||||||
Route::get('spam/profile', 'ReportController@spamProfileForm')->name('report.spam.profile');
|
Route::get('spam/profile', 'ReportController@spamProfileForm')->name('report.spam.profile');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::group(['prefix' => 'account'], function() {
|
Route::group(['prefix' => 'account'], function() {
|
||||||
|
@ -74,6 +86,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware('validemail')->group(fu
|
||||||
Route::get('home', 'SettingsController@home')->name('settings');
|
Route::get('home', 'SettingsController@home')->name('settings');
|
||||||
Route::post('home', 'SettingsController@homeUpdate');
|
Route::post('home', 'SettingsController@homeUpdate');
|
||||||
Route::get('avatar', 'SettingsController@avatar')->name('settings.avatar');
|
Route::get('avatar', 'SettingsController@avatar')->name('settings.avatar');
|
||||||
|
Route::post('avatar', 'AvatarController@store');
|
||||||
Route::get('password', 'SettingsController@password')->name('settings.password');
|
Route::get('password', 'SettingsController@password')->name('settings.password');
|
||||||
Route::post('password', 'SettingsController@passwordUpdate');
|
Route::post('password', 'SettingsController@passwordUpdate');
|
||||||
Route::get('email', 'SettingsController@email')->name('settings.email');
|
Route::get('email', 'SettingsController@email')->name('settings.email');
|
||||||
|
@ -83,14 +96,25 @@ Route::domain(config('pixelfed.domain.app'))->middleware('validemail')->group(fu
|
||||||
Route::get('security', 'SettingsController@security')->name('settings.security');
|
Route::get('security', 'SettingsController@security')->name('settings.security');
|
||||||
Route::get('applications', 'SettingsController@applications')->name('settings.applications');
|
Route::get('applications', 'SettingsController@applications')->name('settings.applications');
|
||||||
Route::get('data-export', 'SettingsController@dataExport')->name('settings.dataexport');
|
Route::get('data-export', 'SettingsController@dataExport')->name('settings.dataexport');
|
||||||
Route::get('import', 'SettingsController@dataImport')->name('settings.import');
|
|
||||||
Route::get('import/instagram', 'SettingsController@dataImportInstagram')->name('settings.import.ig');
|
|
||||||
Route::get('developers', 'SettingsController@developers')->name('settings.developers');
|
Route::get('developers', 'SettingsController@developers')->name('settings.developers');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Route::group(['prefix' => 'site'], function() {
|
||||||
|
Route::redirect('/', '/');
|
||||||
|
Route::get('about', 'SiteController@about')->name('site.about');
|
||||||
|
Route::view('help', 'site.help')->name('site.help');
|
||||||
|
Route::view('developer-api', 'site.developer')->name('site.developers');
|
||||||
|
Route::view('fediverse', 'site.fediverse')->name('site.fediverse');
|
||||||
|
Route::view('open-source', 'site.opensource')->name('site.opensource');
|
||||||
|
Route::view('banned-instances', 'site.bannedinstances')->name('site.bannedinstances');
|
||||||
|
Route::view('terms', 'site.terms')->name('site.terms');
|
||||||
|
Route::view('privacy', 'site.privacy')->name('site.privacy');
|
||||||
|
Route::view('platform', 'site.platform')->name('site.platform');
|
||||||
|
Route::view('language', 'site.language')->name('site.language');
|
||||||
|
});
|
||||||
|
|
||||||
Route::group(['prefix' => 'timeline'], function() {
|
Route::group(['prefix' => 'timeline'], function() {
|
||||||
Route::get('/', 'TimelineController@personal')->name('timeline.personal');
|
Route::redirect('/', '/');
|
||||||
Route::post('/', 'StatusController@store');
|
|
||||||
Route::get('public', 'TimelineController@local')->name('timeline.public');
|
Route::get('public', 'TimelineController@local')->name('timeline.public');
|
||||||
Route::post('public', 'StatusController@store');
|
Route::post('public', 'StatusController@store');
|
||||||
});
|
});
|
||||||
|
@ -100,26 +124,12 @@ Route::domain(config('pixelfed.domain.app'))->middleware('validemail')->group(fu
|
||||||
Route::get('{user}.atom', 'ProfileController@showAtomFeed');
|
Route::get('{user}.atom', 'ProfileController@showAtomFeed');
|
||||||
Route::get('{username}/outbox', 'FederationController@userOutbox');
|
Route::get('{username}/outbox', 'FederationController@userOutbox');
|
||||||
Route::get('{user}', function($user) {
|
Route::get('{user}', function($user) {
|
||||||
return redirect('/@'.$user);
|
return redirect('/'.$user);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::group(['prefix' => 'site'], function() {
|
|
||||||
Route::redirect('/', '/');
|
|
||||||
Route::view('about', 'site.about')->name('site.about');
|
|
||||||
Route::view('features', 'site.features')->name('site.features');
|
|
||||||
Route::view('help', 'site.help')->name('site.help');
|
|
||||||
Route::view('fediverse', 'site.fediverse')->name('site.fediverse');
|
|
||||||
Route::view('open-source', 'site.opensource')->name('site.opensource');
|
|
||||||
Route::view('banned-instances', 'site.bannedinstances')->name('site.bannedinstances');
|
|
||||||
Route::view('terms', 'site.terms')->name('site.terms');
|
|
||||||
Route::view('privacy', 'site.privacy')->name('site.privacy');
|
|
||||||
Route::view('platform', 'site.platform')->name('site.platform');
|
|
||||||
Route::view('libraries', 'site.libraries')->name('site.libraries');
|
|
||||||
Route::view('language', 'site.language')->name('site.language');
|
|
||||||
});
|
|
||||||
|
|
||||||
Route::get('p/{username}/{id}/c/{cid}', 'CommentController@show');
|
Route::get('p/{username}/{id}/c/{cid}', 'CommentController@show');
|
||||||
|
Route::get('p/{username}/{id}/c', 'CommentController@showAll');
|
||||||
Route::get('p/{username}/{id}', 'StatusController@show');
|
Route::get('p/{username}/{id}', 'StatusController@show');
|
||||||
Route::get('{username}/saved', 'ProfileController@savedBookmarks');
|
Route::get('{username}/saved', 'ProfileController@savedBookmarks');
|
||||||
Route::get('{username}/followers', 'ProfileController@followers');
|
Route::get('{username}/followers', 'ProfileController@followers');
|
||||||
|
|
Loading…
Reference in a new issue