diff --git a/app/Album.php b/app/Album.php index d19c77b..e375df9 100644 --- a/app/Album.php +++ b/app/Album.php @@ -203,8 +203,6 @@ class Album extends Model return $this->hasMany(Photo::class); } - - public function redirects() { return $this->hasMany(AlbumRedirect::class); @@ -217,13 +215,14 @@ class Album extends Model public function thumbnailUrl($thumbnailName) { + /** @var Photo $photo */ $photo = $this->photos() ->inRandomOrder() ->first(); if (!is_null($photo)) { - return $this->getAlbumSource()->getUrlToPhoto($photo, $thumbnailName); + return $photo->thumbnailUrl($thumbnailName); } // See if any child albums have an image diff --git a/app/Helpers/ConfigHelper.php b/app/Helpers/ConfigHelper.php index ac964b2..e050e5b 100644 --- a/app/Helpers/ConfigHelper.php +++ b/app/Helpers/ConfigHelper.php @@ -2,17 +2,18 @@ namespace App\Helpers; -use App\Album; use App\AlbumSources\AmazonS3Source; use App\AlbumSources\IAlbumSource; use App\AlbumSources\LocalFilesystemSource; use App\AlbumSources\OpenStackSource; -use App\AlbumSources\OpenStackV1Source; use App\AlbumSources\RackspaceSource; use App\Configuration; class ConfigHelper { + /** @var mixed Cache of configuration values */ + private $cache; + public function allowedAlbumViews() { return ['default', 'slideshow']; @@ -133,6 +134,7 @@ class ConfigHelper 'social_facebook_login' => false, 'social_google_login' => false, 'social_twitter_login' => false, + 'social_user_feeds' => false, 'social_user_profiles' => false, 'theme' => 'default', 'twitter_app_id' => '', @@ -142,7 +144,12 @@ class ConfigHelper public function get($key, $defaultIfUnset = true) { - $config = Configuration::where('key', $key)->first(); + if (is_null($this->cache)) + { + $this->loadCache(); + } + + $config = isset($this->cache[$key]) ? $this->cache[$key] : null; if (is_null($config)) { @@ -162,6 +169,7 @@ class ConfigHelper { $results = array(); + /** @var Configuration $config */ foreach (Configuration::all() as $config) { $results[$config->key] = $config->value; @@ -172,13 +180,15 @@ class ConfigHelper public function getOrCreateModel($key) { - $config = Configuration::where('key', $key)->first(); + $config = isset($this->cache[$key]) ? $this->cache[$key] : null; if (is_null($config) || $config === false) { $config = new Configuration(); $config->key = $key; $config->value = ''; $config->save(); + + $this->loadCache(); } return $config; @@ -187,6 +197,18 @@ class ConfigHelper public function isSocialMediaLoginEnabled() { return $this->get('social_facebook_login') || - $this->get('social_twitter_login'); + $this->get('social_twitter_login') || + $this->get('social_google_login'); + } + + private function loadCache() + { + $this->cache = null; + + /** @var Configuration $config */ + foreach (Configuration::all() as $config) + { + $this->cache[$config->key] = $config; + } } } \ No newline at end of file diff --git a/app/Helpers/MiscHelper.php b/app/Helpers/MiscHelper.php index 582f630..4f2b49c 100644 --- a/app/Helpers/MiscHelper.php +++ b/app/Helpers/MiscHelper.php @@ -96,6 +96,16 @@ class MiscHelper ]; } + public static function ensureHasTrailingSlash($string) + { + if (strlen($string) > 0 && substr($string, strlen($string) - 1, 1) != '/') + { + $string .= '/'; + } + + return $string; + } + public static function getEnvironmentFilePath() { return sprintf('%s/.env', dirname(dirname(__DIR__))); diff --git a/app/Helpers/PermissionsHelper.php b/app/Helpers/PermissionsHelper.php index de42934..50649be 100644 --- a/app/Helpers/PermissionsHelper.php +++ b/app/Helpers/PermissionsHelper.php @@ -16,6 +16,25 @@ class PermissionsHelper public function getAlbumIDs($permission = 'list', User $user = null) { $result = []; + + // First check if the anonymous user can do what is being requested - if so, the permission would also inherit + // to logged-in users + $anonymousUsersCan = DB::table('album_permissions_cache') + ->join('permissions', 'permissions.id', '=', 'album_permissions_cache.permission_id') + ->where([ + ['album_permissions_cache.user_id', null], + ['permissions.section', 'album'], + ['permissions.description', $permission] + ]) + ->select('album_permissions_cache.album_id') + ->distinct() + ->get(); + + foreach ($anonymousUsersCan as $item) + { + $result[] = $item->album_id; + } + $query = DB::table('album_permissions_cache') ->join('permissions', 'permissions.id', '=', 'album_permissions_cache.permission_id') ->where([ @@ -29,7 +48,10 @@ class PermissionsHelper foreach ($query as $item) { - $result[] = $item->album_id; + if (!in_array($item->album_id, $result)) + { + $result[] = $item->album_id; + } } return $result; @@ -42,6 +64,23 @@ class PermissionsHelper public function userCan_Album(Album $album, User $user, $permission) { + // First check if the anonymous user can do what is being requested - if so, the permission would also inherit + // to logged-in users + $anonymousUsersCan = DB::table('album_permissions_cache') + ->join('permissions', 'permissions.id', '=', 'album_permissions_cache.permission_id') + ->where([ + ['album_permissions_cache.album_id', $album->id], + ['album_permissions_cache.user_id', null], + ['permissions.section', 'album'], + ['permissions.description', $permission] + ]) + ->count() > 0; + + if ($anonymousUsersCan) + { + return true; + } + return DB::table('album_permissions_cache') ->join('permissions', 'permissions.id', '=', 'album_permissions_cache.permission_id') ->where([ diff --git a/app/Http/Controllers/Admin/AlbumController.php b/app/Http/Controllers/Admin/AlbumController.php index f8582a0..da86365 100644 --- a/app/Http/Controllers/Admin/AlbumController.php +++ b/app/Http/Controllers/Admin/AlbumController.php @@ -23,6 +23,7 @@ use App\Services\AlbumService; use App\Services\PhotoService; use App\Storage; use App\User; +use App\UserActivity; use Illuminate\Http\Request; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Auth; @@ -698,6 +699,9 @@ class AlbumController extends Controller } } + // Add an activity record + $this->createActivityRecord($album, 'album.created'); + // Rebuild the permissions cache $helper = new PermissionsHelper(); $helper->rebuildCache(); @@ -782,6 +786,21 @@ class AlbumController extends Controller return redirect(route('albums.show', ['id' => $id])); } + private function createActivityRecord(Album $album, $type, $activityDateTime = null) + { + if (is_null($activityDateTime)) + { + $activityDateTime = new \DateTime(); + } + + $userActivity = new UserActivity(); + $userActivity->user_id = $this->getUser()->id; + $userActivity->activity_at = $activityDateTime; + $userActivity->type = $type; + $userActivity->album_id = $album->id; + $userActivity->save(); + } + /** * @param $id * @return Album diff --git a/app/Http/Controllers/Admin/DefaultController.php b/app/Http/Controllers/Admin/DefaultController.php index 60bf4b6..8e97e92 100644 --- a/app/Http/Controllers/Admin/DefaultController.php +++ b/app/Http/Controllers/Admin/DefaultController.php @@ -249,6 +249,7 @@ class DefaultController extends Controller 'social_facebook_login', 'social_google_login', 'social_twitter_login', + 'social_user_feeds', 'social_user_profiles' ]; $updateKeys = [ diff --git a/app/Http/Controllers/Admin/PhotoController.php b/app/Http/Controllers/Admin/PhotoController.php index 6846115..273916f 100644 --- a/app/Http/Controllers/Admin/PhotoController.php +++ b/app/Http/Controllers/Admin/PhotoController.php @@ -15,6 +15,8 @@ use App\Photo; use App\Services\PhotoService; use App\Upload; use App\UploadPhoto; +use App\User; +use App\UserActivity; use Illuminate\Http\Request; use App\Http\Controllers\Controller; use Illuminate\Http\UploadedFile; @@ -52,6 +54,15 @@ class PhotoController extends Controller $photoService = new PhotoService($photo); $photoService->analyse($queue_token); + // Log an activity record for the user's feed (remove an existing one as the date may have changed) + $this->removeExistingActivityRecords($photo, 'photo.taken'); + + if (!is_null($photo->taken_at)) + { + // Log an activity record for the user's feed + $this->createActivityRecord($photo, 'photo.taken', $photo->taken_at); + } + $result['is_successful'] = true; } catch (\Exception $ex) @@ -115,6 +126,9 @@ class PhotoController extends Controller $photoService = new PhotoService($photo); $photoService->flip($horizontal, $vertical); + + // Log an activity record for the user's feed + $this->createActivityRecord($photo, 'photo.edited'); } public function move(Request $request, $photoId) @@ -158,6 +172,15 @@ class PhotoController extends Controller $photoService->downloadOriginalToFolder(FileHelper::getQueuePath($queue_token)); $photoService->analyse($queue_token); + // Log an activity record for the user's feed (remove an existing one as the date may have changed) + $this->removeExistingActivityRecords($photo, 'photo.taken'); + + if (!is_null($photo->taken_at)) + { + // Log an activity record for the user's feed + $this->createActivityRecord($photo, 'photo.taken', $photo->taken_at); + } + $result['is_successful'] = true; } catch (\Exception $ex) @@ -209,6 +232,9 @@ class PhotoController extends Controller $photoService = new PhotoService($photo); $photoService->rotate($angle); + + // Log an activity record for the user's feed + $this->createActivityRecord($photo, 'photo.edited'); } /** @@ -253,6 +279,9 @@ class PhotoController extends Controller /** @var File $savedFile */ $savedFile = FileHelper::saveUploadedFile($photoFile, $queueFolder, $photo->storage_file_name); + + $this->removeExistingActivityRecords($photo, 'photo.uploaded'); + $this->removeExistingActivityRecords($photo, 'photo.taken'); } else { @@ -272,6 +301,9 @@ class PhotoController extends Controller $photo->is_analysed = false; $photo->save(); + // Log an activity record for the user's feed + $this->createActivityRecord($photo, 'photo.uploaded'); + $isSuccessful = true; } } @@ -381,6 +413,10 @@ class PhotoController extends Controller $photo->file_size = $savedFile->getSize(); $photo->is_analysed = false; $photo->save(); + + // Log an activity record for the user's feed + // Log an activity record for the user's feed + $this->createActivityRecord($photo, 'photo.uploaded'); } return redirect(route('albums.analyse', [ @@ -566,6 +602,12 @@ class PhotoController extends Controller $photo->save(); } + if (!in_array(strtolower($action), ['delete', 'refresh_thumbnails', 'change_album'])) + { + // Log an activity record for the user's feed + $this->createActivityRecord($photo, 'photo.edited'); + } + if ($changed) { $numberChanged++; @@ -575,6 +617,21 @@ class PhotoController extends Controller return $numberChanged; } + private function createActivityRecord(Photo $photo, $type, $activityDateTime = null) + { + if (is_null($activityDateTime)) + { + $activityDateTime = new \DateTime(); + } + + $userActivity = new UserActivity(); + $userActivity->user_id = $this->getUser()->id; + $userActivity->activity_at = $activityDateTime; + $userActivity->type = $type; + $userActivity->photo_id = $photo->id; + $userActivity->save(); + } + /** * @param $id * @return Album @@ -615,6 +672,20 @@ class PhotoController extends Controller return $photo; } + private function removeExistingActivityRecords(Photo $photo, $type) + { + $existingFeedRecords = UserActivity::where([ + 'user_id' => $this->getUser()->id, + 'photo_id' => $photo->id, + 'type' => $type + ])->get(); + + foreach ($existingFeedRecords as $existingFeedRecord) + { + $existingFeedRecord->delete(); + } + } + private function updatePhotoDetails(Request $request, Album $album) { $numberChanged = 0; diff --git a/app/Http/Controllers/Auth/ActivateController.php b/app/Http/Controllers/Auth/ActivateController.php index ec80827..6c707ad 100644 --- a/app/Http/Controllers/Auth/ActivateController.php +++ b/app/Http/Controllers/Auth/ActivateController.php @@ -47,6 +47,7 @@ class ActivateController extends Controller $request->session()->flash('info', trans('auth.account_activated_message')); + $this->logActivatedActivity($user); $this->sendUserActivatedEmails($user); return redirect($this->redirectPath()); diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 257bee4..f123724 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers\Auth; use App\Facade\Theme; use App\Facade\UserConfig; +use App\Helpers\MiscHelper; use App\Http\Controllers\Controller; use App\User; use Illuminate\Contracts\Routing\UrlGenerator; @@ -30,6 +31,9 @@ class LoginController extends Controller use AuthenticatesUsers; + /** + * @var UrlGenerator + */ protected $generator; /** @@ -37,7 +41,7 @@ class LoginController extends Controller * * @var string */ - protected $redirectTo = '/'; + protected $redirectTo = '/me'; /** * Create a new controller instance. @@ -101,7 +105,15 @@ class LoginController extends Controller */ public function showLoginForm(Request $request) { - $request->getSession()->put('url.intended', $this->generator->previous(false)); + $previousUrl = MiscHelper::ensureHasTrailingSlash($this->generator->previous(false)); + $homeUrl = MiscHelper::ensureHasTrailingSlash(route('home')); + + if (UserConfig::get('social_user_feeds') && (empty($previousUrl) || $previousUrl == $homeUrl)) + { + $previousUrl = route('userActivityFeed'); + } + + $request->getSession()->put('url.intended', $previousUrl); return Theme::render('auth.v2_unified', [ 'active_tab' => 'login', diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index badeb5a..68b1f8d 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -128,6 +128,7 @@ class RegisterController extends Controller if ($user->is_activated) { + $this->logActivatedActivity($user); $this->sendUserActivatedEmails($user); $this->guard()->login($user); } diff --git a/app/Http/Controllers/Gallery/ExploreController.php b/app/Http/Controllers/Gallery/ExploreController.php new file mode 100644 index 0000000..217e562 --- /dev/null +++ b/app/Http/Controllers/Gallery/ExploreController.php @@ -0,0 +1,41 @@ + true, + 'enable_profile_page' => true + ]) + ->orderBy('name') + ->paginate(UserConfig::get('items_per_page')); + + $usersFollowing = UserFollower::where('user_id', $this->getUser()->id) + ->select('following_user_id') + ->get() + ->map(function($f) + { + return $f->following_user_id; + }) + ->toArray(); + + return Theme::render('gallery.explore_users', [ + 'users' => $users, + 'users_following' => $usersFollowing + ]); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Gallery/PhotoCommentController.php b/app/Http/Controllers/Gallery/PhotoCommentController.php index 3ae5db5..ac165ed 100644 --- a/app/Http/Controllers/Gallery/PhotoCommentController.php +++ b/app/Http/Controllers/Gallery/PhotoCommentController.php @@ -12,10 +12,12 @@ use App\Http\Requests\StorePhotoCommentRequest; use App\Mail\ModeratePhotoComment; use App\Mail\PhotoCommentApproved; use App\Mail\PhotoCommentApprovedUser; +use App\Mail\PhotoCommentRepliedTo; use App\Permission; use App\Photo; use App\PhotoComment; use App\User; +use App\UserActivity; use Illuminate\Http\Request; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Auth; @@ -54,6 +56,7 @@ class PhotoCommentController extends Controller $comment->approved_user_id = $this->getUser()->id; $comment->save(); + $this->createUserActivityRecord($comment); $this->notifyAlbumOwnerAndPoster($album, $photo, $comment); $request->getSession()->flash('success', trans('gallery.photo_comment_approved_successfully')); } @@ -191,6 +194,8 @@ class PhotoCommentController extends Controller } else { + // Log an activity record for the user's feed + $this->createUserActivityRecord($comment); $this->notifyAlbumOwnerAndPoster($album, $photo, $comment); $request->getSession()->flash('success', trans('gallery.photo_comment_posted_successfully')); } @@ -223,6 +228,29 @@ class PhotoCommentController extends Controller } } + private function createUserActivityRecord(PhotoComment $comment) + { + if (!is_null($comment->created_user_id)) + { + $userActivity = new UserActivity(); + $userActivity->user_id = $comment->created_user_id; + $userActivity->activity_at = $comment->created_at; + + if (is_null($comment->parent_comment_id)) + { + $userActivity->type = 'photo.commented'; + } + else + { + $userActivity->type = 'photo.comment_replied'; + } + + $userActivity->photo_id = $comment->photo_id; + $userActivity->photo_comment_id = $comment->id; + $userActivity->save(); + } + } + private function loadAlbumPhotoComment($albumUrlAlias, $photoFilename, $commentID, &$album, &$photo, &$comment) { $album = DbHelper::getAlbumByPath($albumUrlAlias); @@ -256,6 +284,22 @@ class PhotoCommentController extends Controller return true; } + /** + * Loads a given comment by its ID. + * @param $id + * @return PhotoComment + */ + private function loadCommentByID($id) + { + $comment = PhotoComment::where('id', intval($id))->first(); + if (is_null($comment)) + { + App::abort(404); + } + + return $comment; + } + /** * Sends an e-mail notification to an album's moderators that a comment is available to moderate. * @param Album $album diff --git a/app/Http/Controllers/Gallery/UserController.php b/app/Http/Controllers/Gallery/UserController.php index 12e4714..6f1f3c3 100644 --- a/app/Http/Controllers/Gallery/UserController.php +++ b/app/Http/Controllers/Gallery/UserController.php @@ -10,14 +10,108 @@ use App\Http\Controllers\Controller; use App\Http\Requests\SaveUserSettingsRequest; use App\Mail\UserChangeEmailRequired; use App\User; +use App\UserActivity; use Illuminate\Support\Collection; use Illuminate\Support\Facades\App; +use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Mail; use Symfony\Component\HttpFoundation\Request; class UserController extends Controller { + public function activityFeed() + { + if (!UserConfig::get('social_user_feeds')) + { + return redirect(route('home')); + } + + return Theme::render('gallery.user_activity_feed', [ + 'user' => $this->getUser() + ]); + } + + public function activityFeedJson() + { + if (!UserConfig::get('social_user_feeds')) + { + return response()->json(['message' => 'Activity feeds not enabled']); + } + + $user = $this->getUser(); + + $result = []; + $activities = UserActivity::with('photo') + ->with('photoComment') + ->with('user') + ->join('user_followers', 'user_followers.following_user_id', '=', 'user_activity.user_id') + ->where([ + 'user_followers.user_id' => $user->id + ]) + ->orderBy('activity_at', 'desc') + ->limit(100) // TODO: make this configurable + ->select('user_activity.*') + ->get(); + + /** @var UserActivity $activity */ + foreach ($activities as $activity) + { + $userName = $activity->user->name; + $userProfileUrl = $activity->user->profileUrl(); + $userAvatar = Theme::gravatarUrl($activity->user->email, 32); + + $newItem = [ + 'activity_at' => date(UserConfig::get('date_format'), strtotime($activity->activity_at)), + 'avatar' => $userAvatar, + 'description' => trans(sprintf('gallery.user_feed_type.%s', $activity->type)) + ]; + + $params = []; + $params['user_name'] = $userName; + $params['user_url'] = $userProfileUrl; + + if (!is_null($activity->photo)) + { + // Check the user has access + if (!$this->getUser()->can('view', $activity->photo)) + { + continue; + } + + $params['photo_name'] = $activity->photo->name; + $params['photo_url'] = $activity->photo->url(); + } + + if (!is_null($activity->album)) + { + // Check the user has access + if (!$this->getUser()->can('view', $activity->album)) + { + continue; + } + + $params['album_name'] = $activity->album->name; + $params['album_url'] = $activity->album->url(); + } + + // Other activity-specific parameters + switch (strtolower($activity->type)) + { + case 'user.created': + $params['app_name'] = UserConfig::get('app_name'); + $params['app_url'] = route('home'); + break; + } + + $newItem['params'] = $params; + + $result[] = $newItem; + } + + return response()->json($result); + } + public function confirmEmailChangeState(Request $request) { $user = $this->getUser(); @@ -38,6 +132,25 @@ class UserController extends Controller return redirect(route('userSettings')); } + public function followUser($idOrAlias) + { + $user = $this->loadUserProfilePage($idOrAlias); + + $isFollowing = $this->getUser()->following()->where('following_user_id', $user->id)->count() > 0; + if (!$isFollowing) + { + $this->getUser()->following()->attach( + $user->id, + [ + 'created_at' => new \DateTime(), + 'updated_at' => new \DateTime() + ] + ); + } + + return response()->json(true); + } + public function resetEmailChangeState(Request $request) { $user = $this->getUser(); @@ -118,16 +231,7 @@ class UserController extends Controller public function show(Request $request, $idOrAlias) { - // If a user has a profile alias set, their profile page cannot be accessed by the ID - $user = User::where(DB::raw('COALESCE(profile_alias, id)'), strtolower($idOrAlias))->first(); - - if (is_null($user)) - { - App::abort(404); - return null; - } - - $this->authorizeForUser($this->getUser(), 'view', $user); + $user = $this->loadUserProfilePage($idOrAlias); $albums = $this->getAlbumsForUser($user); $albumIDs = $this->getAlbumIDsForUser($user); @@ -136,16 +240,115 @@ class UserController extends Controller $daysInMonth = $this->getDaysInMonths(); + // Only logged-in users can follow other users (and if it's not their own page!) + $canFollow = !$this->getUser()->isAnonymous() && $this->getUser()->id != $user->id; + $isFollowing = false; + + if ($canFollow) + { + // Is the current user following this user? + $isFollowing = $this->getUser()->following()->where('following_user_id', $user->id)->count() > 0; + } + return Theme::render('gallery.user_profile', [ + 'active_tab' => $request->get('tab'), 'activity_taken' => $this->constructActivityGrid($activity['taken']), 'activity_uploaded' => $this->constructActivityGrid($activity['uploaded']), 'albums' => $albums, 'cameras' => $cameras, + 'can_follow' => $canFollow, + 'is_following' => $isFollowing, 'month_days' => $daysInMonth, 'user' => $user ]); } + public function showFeedJson(Request $request, $idOrAlias) + { + $user = $this->loadUserProfilePage($idOrAlias); + + $result = []; + $activities = UserActivity::with('photo') + ->with('photoComment') + ->with('album') + ->where([ + 'user_id' => $user->id + ]) + ->orderBy('activity_at', 'desc') + ->limit(100) // TODO: make this configurable + ->get(); + + $userName = $user->name; + $userProfileUrl = $user->profileUrl(); + $userAvatar = Theme::gravatarUrl($user->email, 32); + + /** @var UserActivity $activity */ + foreach ($activities as $activity) + { + $newItem = [ + 'activity_at' => date(UserConfig::get('date_format'), strtotime($activity->activity_at)), + 'avatar' => $userAvatar, + 'description' => trans(sprintf('gallery.user_feed_type.%s', $activity->type)) + ]; + + $params = []; + $params['user_name'] = $userName; + $params['user_url'] = $userProfileUrl; + + if (!is_null($activity->photo)) + { + // Check the user has access + if (!$this->getUser()->can('view', $activity->photo)) + { + continue; + } + + $params['photo_name'] = $activity->photo->name; + $params['photo_url'] = $activity->photo->url(); + } + + if (!is_null($activity->album)) + { + // Check the user has access + if (!$this->getUser()->can('view', $activity->album)) + { + continue; + } + + $params['album_name'] = $activity->album->name; + $params['album_url'] = $activity->album->url(); + } + + // Other activity-specific parameters + switch (strtolower($activity->type)) + { + case 'user.created': + $params['app_name'] = UserConfig::get('app_name'); + $params['app_url'] = route('home'); + break; + } + + $newItem['params'] = $params; + + $result[] = $newItem; + } + + return response()->json($result); + } + + public function unFollowUser($idOrAlias) + { + $user = $this->loadUserProfilePage($idOrAlias); + + $isFollowing = $this->getUser()->following()->where('following_user_id', $user->id)->count() > 0; + if ($isFollowing) + { + $this->getUser()->following()->detach($user->id); + } + + return response()->json(true); + } + private function constructActivityGrid(Collection $collection) { $results = []; @@ -300,6 +503,26 @@ class UserController extends Controller return $results; } + /** + * @param $idOrAlias + * @return User + */ + private function loadUserProfilePage($idOrAlias) + { + // If a user has a profile alias set, their profile page cannot be accessed by the ID + $user = User::where(DB::raw('COALESCE(NULLIF(profile_alias, \'\'), id)'), strtolower($idOrAlias))->first(); + + if (is_null($user)) + { + App::abort(404); + return null; + } + + $this->authorizeForUser($this->getUser(), 'view', $user); + + return $user; + } + private function sendEmailChangeConfirmationEmail(User $user, $newEmailAddress) { $oldEmailAddress = $user->email; diff --git a/app/Policies/PhotoPolicy.php b/app/Policies/PhotoPolicy.php index eaa0626..bc87ed3 100644 --- a/app/Policies/PhotoPolicy.php +++ b/app/Policies/PhotoPolicy.php @@ -83,4 +83,15 @@ class PhotoPolicy return $user->can('post-comment', $photo->album); } + + public function view(User $user, Photo $photo) + { + if ($user->id == $photo->user_id) + { + // The photo's owner can do everything + return true; + } + + return $user->can('view', $photo->album); + } } diff --git a/app/Traits/ActivatesUsers.php b/app/Traits/ActivatesUsers.php index 93caf08..1c3a6f7 100644 --- a/app/Traits/ActivatesUsers.php +++ b/app/Traits/ActivatesUsers.php @@ -4,10 +4,25 @@ namespace App\Traits; use App\Mail\UserSelfActivated; use App\User; +use App\UserActivity; use Illuminate\Support\Facades\Mail; trait ActivatesUsers { + private function logActivatedActivity(User $createdUser, $activityDateTime = null) + { + if (is_null($activityDateTime)) + { + $activityDateTime = new \DateTime(); + } + + $userActivity = new UserActivity(); + $userActivity->user_id = $createdUser->id; + $userActivity->activity_at = $activityDateTime; + $userActivity->type = 'user.created'; + $userActivity->save(); + } + private function sendUserActivatedEmails(User $createdUser) { $adminUsers = User::where('is_admin', true)->get(); diff --git a/app/User.php b/app/User.php index 0e7f501..92a25f3 100644 --- a/app/User.php +++ b/app/User.php @@ -55,6 +55,30 @@ class User extends Authenticatable return $this->hasMany(Album::class); } + public function feedJsonUrl() + { + return route('viewUserFeedJson', [ + 'idOrAlias' => (!empty($this->profile_alias) ? trim(strtolower($this->profile_alias)) : $this->id) + ]); + } + + public function followers() + { + return $this->belongsToMany(User::class, 'user_followers', 'following_user_id', 'user_id'); + } + + public function following() + { + return $this->belongsToMany(User::class, 'user_followers', 'user_id', 'following_user_id'); + } + + public function followUrl() + { + return route('followUser', [ + 'idOrAlias' => $this->profileAliasForUrl() + ]); + } + public function groups() { return $this->belongsToMany(Group::class, 'user_groups'); @@ -65,10 +89,15 @@ class User extends Authenticatable return $this->id == -1 && $this->name == 'Anonymous'; } + public function profileAliasForUrl() + { + return (!empty($this->profile_alias) ? trim(strtolower($this->profile_alias)) : $this->id); + } + public function profileUrl() { return route('viewUser', [ - 'idOrAlias' => (!empty($this->profile_alias) ? trim(strtolower($this->profile_alias)) : $this->id) + 'idOrAlias' => $this->profileAliasForUrl() ]); } @@ -76,4 +105,11 @@ class User extends Authenticatable { return trim(!empty($this->profile_alias) ? $this->profile_alias : $this->name); } + + public function unFollowUrl() + { + return route('unFollowUser', [ + 'idOrAlias' => $this->profileAliasForUrl() + ]); + } } diff --git a/app/UserActivity.php b/app/UserActivity.php new file mode 100644 index 0000000..12f2823 --- /dev/null +++ b/app/UserActivity.php @@ -0,0 +1,30 @@ +belongsTo(Album::class); + } + + public function photo() + { + return $this->belongsTo(Photo::class); + } + + public function photoComment() + { + return $this->belongsTo(PhotoComment::class); + } + + public function user() + { + return $this->belongsTo(User::class); + } +} diff --git a/app/UserFollower.php b/app/UserFollower.php new file mode 100644 index 0000000..1a6fe03 --- /dev/null +++ b/app/UserFollower.php @@ -0,0 +1,9 @@ +bigIncrements('id'); + $table->unsignedInteger('user_id'); + $table->dateTime('activity_at'); + $table->string('type', 100); + $table->unsignedBigInteger('photo_id')->nullable(true); + $table->unsignedBigInteger('photo_comment_id')->nullable(true); + $table->timestamps(); + + $table->foreign('user_id') + ->references('id')->on('users') + ->onDelete('cascade'); + $table->foreign('photo_id') + ->references('id')->on('photos') + ->onDelete('cascade'); + $table->foreign('photo_comment_id') + ->references('id')->on('photo_comments') + ->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('user_activity'); + } +} diff --git a/database/migrations/2018_10_10_130100_create_user_followers_table.php b/database/migrations/2018_10_10_130100_create_user_followers_table.php new file mode 100644 index 0000000..cd93102 --- /dev/null +++ b/database/migrations/2018_10_10_130100_create_user_followers_table.php @@ -0,0 +1,40 @@ +unsignedInteger('user_id'); + $table->unsignedInteger('following_user_id'); + $table->timestamps(); + + $table->foreign('user_id') + ->references('id')->on('users') + ->onDelete('cascade'); + + $table->foreign('following_user_id') + ->references('id')->on('users') + ->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('user_followers'); + } +} diff --git a/database/migrations/2018_11_18_205607_add_album_activity_column.php b/database/migrations/2018_11_18_205607_add_album_activity_column.php new file mode 100644 index 0000000..7be467a --- /dev/null +++ b/database/migrations/2018_11_18_205607_add_album_activity_column.php @@ -0,0 +1,39 @@ +unsignedInteger('album_id')->nullable(true); + + $table->foreign('album_id') + ->references('id')->on('albums') + ->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('user_activity', function (Blueprint $table) + { + $table->dropForeign('user_activity_album_id_foreign'); + $table->dropColumn('album_id'); + }); + } +} diff --git a/public/js/blue-twilight.js b/public/js/blue-twilight.js index 272d0bb..15c3981 100644 --- a/public/js/blue-twilight.js +++ b/public/js/blue-twilight.js @@ -41534,6 +41534,50 @@ module.exports = function(Chart) { },{"25":25,"45":45,"6":6}]},{},[7])(7) }); +/** + * This model is used by gallery/explore_users.blade.php, to handle following/unfollowing users profiles. + * @constructor + */ +function ExploreUsersViewModel(urls) +{ + this.el = '#explore-users-app'; + + this.data = { + }; + + this.computed = { + }; + + this.methods = { + followUser: function(e) + { + var userIDToFollow = $(e.target).data('user-id'); + var urlToPost = urls.follow_user_url.replace('/-1/', '/' + userIDToFollow + '/'); + + $.post(urlToPost, '', function(data) + { + window.location.reload(true); + }); + + e.preventDefault(); + return false; + }, + unFollowUser: function(e) + { + var userIDToUnfollow = $(e.target).data('user-id'); + var urlToPost = urls.unfollow_user_url.replace('/-1/', '/' + userIDToUnfollow + '/'); + + $.post(urlToPost, '', function(data) + { + window.location.reload(true); + }); + + e.preventDefault(); + return false; + } + }; +} + /** * This model is used by gallery/photo.blade.php, to handle comments and individual photo actions. * @constructor @@ -41610,6 +41654,121 @@ function PhotoViewModel(urls) { } }; } + +/** + * This model is used by gallery/user_profile.blade.php, to handle a user's profile. + * @constructor + */ +function UserViewModel(urls) +{ + this.el = '#user-app'; + + this.data = { + feed_items: [], + is_loading: true, + selected_view: 'profile', + user_id: 0 + }; + + this.computed = { + isFeed: function() { + return this.selected_view === 'feed'; + }, + isProfile: function() { + return this.selected_view === 'profile'; + } + }; + + this.methods = { + followUser: function(e) + { + $.post(urls.follow_user_url, '', function(data) + { + window.location.reload(true); + }); + + e.preventDefault(); + return false; + }, + loadFeedItems: function(e) + { + var self = this; + + $.get(urls.feed_url, function (data) + { + for (var i = 0; i < data.length; i++) + { + // User name + if (data[i].params.user_name && data[i].params.user_url) + { + data[i].description = data[i].description + .replace( + ':user_name', + '' + data[i].params.user_name + '' + ); + } + + // Photo name + if (data[i].params.photo_name && data[i].params.photo_url) + { + data[i].description = data[i].description + .replace( + ':photo_name', + '' + data[i].params.photo_name + '' + ); + } + + // Album name + if (data[i].params.album_name && data[i].params.album_url) + { + data[i].description = data[i].description + .replace( + ':album_name', + '' + data[i].params.album_name + '' + ); + } + + // App name + if (data[i].params.app_name && data[i].params.app_url) + { + data[i].description = data[i].description + .replace( + ':app_name', + '' + data[i].params.app_name + '' + ); + } + } + + self.feed_items = data; + self.is_loading = false; + }); + }, + switchToFeed: function(e) { + this.selected_view = 'feed'; + history.pushState('', '', urls.current_url + '?tab=feed'); + + e.preventDefault(); + return false; + }, + switchToProfile: function(e) { + this.selected_view = 'profile'; + history.pushState('', '', urls.current_url + '?tab=profile'); + + e.preventDefault(); + return false; + }, + unFollowUser: function(e) + { + $.post(urls.unfollow_user_url, '', function(data) + { + window.location.reload(true); + }); + + e.preventDefault(); + return false; + } + }; +} function StorageLocationViewModel() { this.el = '#storage-options'; diff --git a/public/js/blue-twilight.min.js b/public/js/blue-twilight.min.js index 270e5b1..f13e7d4 100644 --- a/public/js/blue-twilight.min.js +++ b/public/js/blue-twilight.min.js @@ -1 +1 @@ -function AboutViewModel(t){this.el="#about-app",this.data={can_upgrade:!1,is_loading:!0,version_body:"",version_date:"",version_name:"",version_url:""},this.computed={},this.methods={init:function(){var e=this;$.ajax(t.latest_release_url,{complete:function(){e.is_loading=!1},dataType:"json",error:function(t,e,n){},method:"GET",success:function(t){e.version_body=t.body,e.version_date=t.publish_date,e.version_name=t.name,e.version_url=t.url,e.can_upgrade=t.can_upgrade}})}}}function CreateAlbumViewModel(){this.el="#create-album-app",this.data={is_inherit_permissions:!0,is_private:!1,parent_id:""},this.computed={isParentAlbum:function(){return""==this.parent_id},isPrivateDisabled:function(){return!this.isParentAlbum&&this.is_inherit_permissions}}}function EditAlbumViewModel(){this.el="#edit-album-app",this.data={parent_id:""},this.computed={isParentAlbum:function(){return""==this.parent_id}}}function SettingsViewModel(t,e){this.el="#settings-app",this.data={is_rebuilding_permissions_cache:!1},this.methods={rebuildPermissionsCache:function(n){var i=this;return $.ajax(t.rebuild_permissions_cache,{complete:function(){i.is_rebuilding_permissions_cache=!1},dataType:"json",error:function(t,n,i){alert(e.permissions_cache_rebuild_failed)},method:"POST",success:function(t){alert(e.permissions_cache_rebuild_succeeded)}}),n.preventDefault(),!1}}}function AnalyseAlbumViewModel(){this.el="#analyse-album",this.data={imagesFailed:[],imagesToAnalyse:[],imagesInProgress:[],imagesRecentlyCompleted:[],numberSuccessful:0,numberFailed:0},this.computed={failedPercentage:function(){var t=0;return this.numberTotal>0&&(t=this.numberFailed/this.numberTotal*100),t.toFixed(2)+"%"},isCompleted:function(){return this.numberTotal>0&&this.numberSuccessful+this.numberFailed>=this.numberTotal},latestCompletedImages:function(){var t=this.imagesRecentlyCompleted.length-3<0?0:this.imagesRecentlyCompleted.length-3,e=t+3;return this.imagesRecentlyCompleted.slice(t,e)},numberTotal:function(){return this.imagesToAnalyse.length},successfulPercentage:function(){var t=0;return this.numberTotal>0&&(t=this.numberSuccessful/this.numberTotal*100),t.toFixed(2)+"%"}},this.methods={analyseImage:function(t){var e=this;this.imagesToAnalyse.push(t),$.ajax(t.url,{beforeSend:function(){e.imagesInProgress.push(t)},dataType:"json",error:function(n,i,r){e.numberFailed++,e.imagesFailed.push({name:t.name,reason:i}),t.isSuccessful=!1,t.isPending=!1},method:"POST",success:function(n){if(n.is_successful){e.numberSuccessful++,t.isSuccessful=!0,t.isPending=!1,e.imagesRecentlyCompleted.push(t);var i=e.imagesInProgress.indexOf(t);i>-1&&e.imagesInProgress.splice(i,1)}else e.numberFailed++,e.imagesFailed.push({name:t.name,reason:n.message}),t.isSuccessful=!1,t.isPending=!1}})}}}function AnalyseImageViewModel(t){this.isPending=!0,this.isSuccessful=!1,this.name=t.name,this.photoID=t.photo_id,this.url=t.url}function EditPhotosViewModel(t,e,n){this.el="#photos-tab",this.data={albums:[],bulkModifyMethod:"",isSubmitting:!1,photoIDs:[],photoIDsAvailable:[],selectAllInAlbum:0},this.methods={bulkModifySelected:function(t){if(this.isSubmitting)return!0;var n=this,i=$(t.target).closest("form");return"change_album"===this.bulkModifyMethod?(this.promptForNewAlbum(function(t){var e=$("select",t).val();$('input[name="new-album-id"]',i).val(e),n.isSubmitting=!0,$('button[name="bulk-apply"]',i).click(),_bt_showLoadingModal()}),t.preventDefault(),!1):"delete"!==this.bulkModifyMethod||(bootbox.dialog({message:e.delete_bulk_confirm_message,title:e.delete_bulk_confirm_title,buttons:{cancel:{label:e.action_cancel,className:"btn-secondary"},confirm:{label:e.action_delete,className:"btn-danger",callback:function(){n.isSubmitting=!0,$('button[name="bulk-apply"]',i).click(),_bt_showLoadingModal()}}}}),t.preventDefault(),!1)},changeAlbum:function(t){this.selectPhotoSingle(t.target);var e=this.photoIDs[0];return this.photoIDs=[],this.promptForNewAlbum(function(t){var i=$("select",t).val();$.post(n.move_photo.replace(/\/0$/,"/"+e),{new_album_id:i},function(){window.location.reload()})}),t.preventDefault(),!1},deletePhoto:function(t){this.selectPhotoSingle(t.target);var i=this.photoIDs[0];return this.photoIDs=[],bootbox.dialog({message:e.delete_confirm_message,title:e.delete_confirm_title,buttons:{cancel:{label:e.action_cancel,className:"btn-secondary"},confirm:{label:e.action_delete,className:"btn-danger",callback:function(){var t=n.delete_photo;t=t.replace(/\/0$/,"/"+i),$(".loading",parent).show(),$.post(t,{_method:"DELETE"},function(t){window.location.reload()})}}}}),t.preventDefault(),!1},flip:function(t,e,i){var r=n.flip_photo;r=r.replace("/0/","/"+this.photoIDs[0]+"/"),r=t?r.replace(/\/-1\//,"/1/"):r.replace(/\/-1\//,"/0/"),r=e?r.replace(/\/-2$/,"/1"):r.replace(/\/-2$/,"/0"),$(".loading",i).show(),$.post(r,function(){var t=$("img.photo-thumbnail",i),e=t.data("original-src");t.attr("src",e+"&_="+(new Date).getTime()),$(".loading",i).hide()}),this.photoIDs=[]},flipBoth:function(t){return this.selectPhotoSingle(t.target),this.flip(!0,!0,$(t.target).closest(".photo")),t.preventDefault(),!1},flipHorizontal:function(t){return this.selectPhotoSingle(t.target),this.flip(!0,!1,$(t.target).closest(".photo")),t.preventDefault(),!1},flipVertical:function(t){return this.selectPhotoSingle(t.target),this.flip(!1,!0,$(t.target).closest(".photo")),t.preventDefault(),!1},isPhotoSelected:function(t){return this.photoIDs.indexOf(t)>-1?"checked":""},promptForNewAlbum:function(n){for(var i=this.albums,r=$("",t.querySelectorAll("[msallowcapture^='']").length&&m.push("[*^$]="+L+"*(?:''|\"\")"),t.querySelectorAll("[selected]").length||m.push("\\["+L+"*(?:value|"+F+")"),t.querySelectorAll("[id~="+_+"-]").length||m.push("~="),t.querySelectorAll(":checked").length||m.push(":checked"),t.querySelectorAll("a#"+_+"+*").length||m.push(".#.+[+~]")}),lt(function(t){t.innerHTML="";var e=f.createElement("input");e.setAttribute("type","hidden"),t.appendChild(e).setAttribute("name","D"),t.querySelectorAll("[name=d]").length&&m.push("name"+L+"*[*^$|!~]?="),2!==t.querySelectorAll(":enabled").length&&m.push(":enabled",":disabled"),p.appendChild(t).disabled=!0,2!==t.querySelectorAll(":disabled").length&&m.push(":enabled",":disabled"),t.querySelectorAll("*,:x"),m.push(",.*:")})),(n.matchesSelector=Q.test(y=p.matches||p.webkitMatchesSelector||p.mozMatchesSelector||p.oMatchesSelector||p.msMatchesSelector))&<(function(t){n.disconnectedMatch=y.call(t,"*"),y.call(t,"[s!='']:x"),v.push("!=",j)}),m=m.length&&new RegExp(m.join("|")),v=v.length&&new RegExp(v.join("|")),e=Q.test(p.compareDocumentPosition),b=e||Q.test(p.contains)?function(t,e){var n=9===t.nodeType?t.documentElement:t,i=e&&e.parentNode;return t===i||!(!i||1!==i.nodeType||!(n.contains?n.contains(i):t.compareDocumentPosition&&16&t.compareDocumentPosition(i)))}:function(t,e){if(e)for(;e=e.parentNode;)if(e===t)return!0;return!1},D=e?function(t,e){if(t===e)return d=!0,0;var i=!t.compareDocumentPosition-!e.compareDocumentPosition;return i||(1&(i=(t.ownerDocument||t)===(e.ownerDocument||e)?t.compareDocumentPosition(e):1)||!n.sortDetached&&e.compareDocumentPosition(t)===i?t===f||t.ownerDocument===x&&b(x,t)?-1:e===f||e.ownerDocument===x&&b(x,e)?1:c?N(c,t)-N(c,e):0:4&i?-1:1)}:function(t,e){if(t===e)return d=!0,0;var n,i=0,r=t.parentNode,o=e.parentNode,a=[t],s=[e];if(!r||!o)return t===f?-1:e===f?1:r?-1:o?1:c?N(c,t)-N(c,e):0;if(r===o)return ct(t,e);for(n=t;n=n.parentNode;)a.unshift(n);for(n=e;n=n.parentNode;)s.unshift(n);for(;a[i]===s[i];)i++;return i?ct(a[i],s[i]):a[i]===x?-1:s[i]===x?1:0},f):f},ot.matches=function(t,e){return ot(t,null,null,e)},ot.matchesSelector=function(t,e){if((t.ownerDocument||t)!==f&&h(t),e=e.replace(q,"='$1']"),n.matchesSelector&&g&&!T[e+" "]&&(!v||!v.test(e))&&(!m||!m.test(e)))try{var i=y.call(t,e);if(i||n.disconnectedMatch||t.document&&11!==t.document.nodeType)return i}catch(t){}return ot(e,f,null,[t]).length>0},ot.contains=function(t,e){return(t.ownerDocument||t)!==f&&h(t),b(t,e)},ot.attr=function(t,e){(t.ownerDocument||t)!==f&&h(t);var r=i.attrHandle[e.toLowerCase()],o=r&&O.call(i.attrHandle,e.toLowerCase())?r(t,e,!g):void 0;return void 0!==o?o:n.attributes||!g?t.getAttribute(e):(o=t.getAttributeNode(e))&&o.specified?o.value:null},ot.escape=function(t){return(t+"").replace(et,nt)},ot.error=function(t){throw new Error("Syntax error, unrecognized expression: "+t)},ot.uniqueSort=function(t){var e,i=[],r=0,o=0;if(d=!n.detectDuplicates,c=!n.sortStable&&t.slice(0),t.sort(D),d){for(;e=t[o++];)e===t[o]&&(r=i.push(o));for(;r--;)t.splice(i[r],1)}return c=null,t},r=ot.getText=function(t){var e,n="",i=0,o=t.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof t.textContent)return t.textContent;for(t=t.firstChild;t;t=t.nextSibling)n+=r(t)}else if(3===o||4===o)return t.nodeValue}else for(;e=t[i++];)n+=r(e);return n},(i=ot.selectors={cacheLength:50,createPseudo:st,match:U,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(t){return t[1]=t[1].replace(Z,tt),t[3]=(t[3]||t[4]||t[5]||"").replace(Z,tt),"~="===t[2]&&(t[3]=" "+t[3]+" "),t.slice(0,4)},CHILD:function(t){return t[1]=t[1].toLowerCase(),"nth"===t[1].slice(0,3)?(t[3]||ot.error(t[0]),t[4]=+(t[4]?t[5]+(t[6]||1):2*("even"===t[3]||"odd"===t[3])),t[5]=+(t[7]+t[8]||"odd"===t[3])):t[3]&&ot.error(t[0]),t},PSEUDO:function(t){var e,n=!t[6]&&t[2];return U.CHILD.test(t[0])?null:(t[3]?t[2]=t[4]||t[5]||"":n&&V.test(n)&&(e=a(n,!0))&&(e=n.indexOf(")",n.length-e)-n.length)&&(t[0]=t[0].slice(0,e),t[2]=n.slice(0,e)),t.slice(0,3))}},filter:{TAG:function(t){var e=t.replace(Z,tt).toLowerCase();return"*"===t?function(){return!0}:function(t){return t.nodeName&&t.nodeName.toLowerCase()===e}},CLASS:function(t){var e=k[t+" "];return e||(e=new RegExp("(^|"+L+")"+t+"("+L+"|$)"))&&k(t,function(t){return e.test("string"==typeof t.className&&t.className||void 0!==t.getAttribute&&t.getAttribute("class")||"")})},ATTR:function(t,e,n){return function(i){var r=ot.attr(i,t);return null==r?"!="===e:!e||(r+="","="===e?r===n:"!="===e?r!==n:"^="===e?n&&0===r.indexOf(n):"*="===e?n&&r.indexOf(n)>-1:"$="===e?n&&r.slice(-n.length)===n:"~="===e?(" "+r.replace(H," ")+" ").indexOf(n)>-1:"|="===e&&(r===n||r.slice(0,n.length+1)===n+"-"))}},CHILD:function(t,e,n,i,r){var o="nth"!==t.slice(0,3),a="last"!==t.slice(-4),s="of-type"===e;return 1===i&&0===r?function(t){return!!t.parentNode}:function(e,n,l){var u,c,d,h,f,p,g=o!==a?"nextSibling":"previousSibling",m=e.parentNode,v=s&&e.nodeName.toLowerCase(),y=!l&&!s,b=!1;if(m){if(o){for(;g;){for(h=e;h=h[g];)if(s?h.nodeName.toLowerCase()===v:1===h.nodeType)return!1;p=g="only"===t&&!p&&"nextSibling"}return!0}if(p=[a?m.firstChild:m.lastChild],a&&y){for(b=(f=(u=(c=(d=(h=m)[_]||(h[_]={}))[h.uniqueID]||(d[h.uniqueID]={}))[t]||[])[0]===w&&u[1])&&u[2],h=f&&m.childNodes[f];h=++f&&h&&h[g]||(b=f=0)||p.pop();)if(1===h.nodeType&&++b&&h===e){c[t]=[w,f,b];break}}else if(y&&(b=f=(u=(c=(d=(h=e)[_]||(h[_]={}))[h.uniqueID]||(d[h.uniqueID]={}))[t]||[])[0]===w&&u[1]),!1===b)for(;(h=++f&&h&&h[g]||(b=f=0)||p.pop())&&((s?h.nodeName.toLowerCase()!==v:1!==h.nodeType)||!++b||(y&&((c=(d=h[_]||(h[_]={}))[h.uniqueID]||(d[h.uniqueID]={}))[t]=[w,b]),h!==e)););return(b-=r)===i||b%i==0&&b/i>=0}}},PSEUDO:function(t,e){var n,r=i.pseudos[t]||i.setFilters[t.toLowerCase()]||ot.error("unsupported pseudo: "+t);return r[_]?r(e):r.length>1?(n=[t,t,"",e],i.setFilters.hasOwnProperty(t.toLowerCase())?st(function(t,n){for(var i,o=r(t,e),a=o.length;a--;)t[i=N(t,o[a])]=!(n[i]=o[a])}):function(t){return r(t,0,n)}):r}},pseudos:{not:st(function(t){var e=[],n=[],i=s(t.replace(W,"$1"));return i[_]?st(function(t,e,n,r){for(var o,a=i(t,null,r,[]),s=t.length;s--;)(o=a[s])&&(t[s]=!(e[s]=o))}):function(t,r,o){return e[0]=t,i(e,null,o,n),e[0]=null,!n.pop()}}),has:st(function(t){return function(e){return ot(t,e).length>0}}),contains:st(function(t){return t=t.replace(Z,tt),function(e){return(e.textContent||e.innerText||r(e)).indexOf(t)>-1}}),lang:st(function(t){return Y.test(t||"")||ot.error("unsupported lang: "+t),t=t.replace(Z,tt).toLowerCase(),function(e){var n;do{if(n=g?e.lang:e.getAttribute("xml:lang")||e.getAttribute("lang"))return(n=n.toLowerCase())===t||0===n.indexOf(t+"-")}while((e=e.parentNode)&&1===e.nodeType);return!1}}),target:function(e){var n=t.location&&t.location.hash;return n&&n.slice(1)===e.id},root:function(t){return t===p},focus:function(t){return t===f.activeElement&&(!f.hasFocus||f.hasFocus())&&!!(t.type||t.href||~t.tabIndex)},enabled:ft(!1),disabled:ft(!0),checked:function(t){var e=t.nodeName.toLowerCase();return"input"===e&&!!t.checked||"option"===e&&!!t.selected},selected:function(t){return t.parentNode&&t.parentNode.selectedIndex,!0===t.selected},empty:function(t){for(t=t.firstChild;t;t=t.nextSibling)if(t.nodeType<6)return!1;return!0},parent:function(t){return!i.pseudos.empty(t)},header:function(t){return G.test(t.nodeName)},input:function(t){return K.test(t.nodeName)},button:function(t){var e=t.nodeName.toLowerCase();return"input"===e&&"button"===t.type||"button"===e},text:function(t){var e;return"input"===t.nodeName.toLowerCase()&&"text"===t.type&&(null==(e=t.getAttribute("type"))||"text"===e.toLowerCase())},first:pt(function(){return[0]}),last:pt(function(t,e){return[e-1]}),eq:pt(function(t,e,n){return[n<0?n+e:n]}),even:pt(function(t,e){for(var n=0;n=0;)t.push(i);return t}),gt:pt(function(t,e,n){for(var i=n<0?n+e:n;++i1?function(e,n,i){for(var r=t.length;r--;)if(!t[r](e,n,i))return!1;return!0}:t[0]}function _t(t,e,n,i,r){for(var o,a=[],s=0,l=t.length,u=null!=e;s-1&&(o[u]=!(a[u]=d))}}else v=_t(v===a?v.splice(p,v.length):v),r?r(null,a,v,l):I.apply(a,v)})}function wt(t){for(var e,n,r,o=t.length,a=i.relative[t[0].type],s=a||i.relative[" "],l=a?1:0,c=yt(function(t){return t===e},s,!0),d=yt(function(t){return N(e,t)>-1},s,!0),h=[function(t,n,i){var r=!a&&(i||n!==u)||((e=n).nodeType?c(t,n,i):d(t,n,i));return e=null,r}];l1&&bt(h),l>1&&vt(t.slice(0,l-1).concat({value:" "===t[l-2].type?"*":""})).replace(W,"$1"),n,l0,r=t.length>0,o=function(o,a,s,l,c){var d,p,m,v=0,y="0",b=o&&[],_=[],x=u,C=o||r&&i.find.TAG("*",c),k=w+=null==x?1:Math.random()||.1,S=C.length;for(c&&(u=a===f||a||c);y!==S&&null!=(d=C[y]);y++){if(r&&d){for(p=0,a||d.ownerDocument===f||(h(d),s=!g);m=t[p++];)if(m(d,a||f,s)){l.push(d);break}c&&(w=k)}n&&((d=!m&&d)&&v--,o&&b.push(d))}if(v+=y,n&&y!==v){for(p=0;m=e[p++];)m(b,_,a,s);if(o){if(v>0)for(;y--;)b[y]||_[y]||(_[y]=M.call(l));_=_t(_)}I.apply(l,_),c&&!o&&_.length>0&&v+e.length>1&&ot.uniqueSort(l)}return c&&(w=k,u=x),b};return n?st(o):o}(o,r))).selector=t}return s},l=ot.select=function(t,e,n,r){var o,l,u,c,d,h="function"==typeof t&&t,f=!r&&a(t=h.selector||t);if(n=n||[],1===f.length){if((l=f[0]=f[0].slice(0)).length>2&&"ID"===(u=l[0]).type&&9===e.nodeType&&g&&i.relative[l[1].type]){if(!(e=(i.find.ID(u.matches[0].replace(Z,tt),e)||[])[0]))return n;h&&(e=e.parentNode),t=t.slice(l.shift().value.length)}for(o=U.needsContext.test(t)?0:l.length;o--&&(u=l[o],!i.relative[c=u.type]);)if((d=i.find[c])&&(r=d(u.matches[0].replace(Z,tt),J.test(l[0].type)&>(e.parentNode)||e))){if(l.splice(o,1),!(t=r.length&&vt(l)))return I.apply(n,r),n;break}}return(h||s(t,f))(r,e,!g,n,!e||J.test(t)&>(e.parentNode)||e),n},n.sortStable=_.split("").sort(D).join("")===_,n.detectDuplicates=!!d,h(),n.sortDetached=lt(function(t){return 1&t.compareDocumentPosition(f.createElement("fieldset"))}),lt(function(t){return t.innerHTML="","#"===t.firstChild.getAttribute("href")})||ut("type|href|height|width",function(t,e,n){if(!n)return t.getAttribute(e,"type"===e.toLowerCase()?1:2)}),n.attributes&<(function(t){return t.innerHTML="",t.firstChild.setAttribute("value",""),""===t.firstChild.getAttribute("value")})||ut("value",function(t,e,n){if(!n&&"input"===t.nodeName.toLowerCase())return t.defaultValue}),lt(function(t){return null==t.getAttribute("disabled")})||ut(F,function(t,e,n){var i;if(!n)return!0===t[e]?e.toLowerCase():(i=t.getAttributeNode(e))&&i.specified?i.value:null}),ot}(t);m.find=w,m.expr=w.selectors,m.expr[":"]=m.expr.pseudos,m.uniqueSort=m.unique=w.uniqueSort,m.text=w.getText,m.isXMLDoc=w.isXML,m.contains=w.contains,m.escapeSelector=w.escape;var C=function(t,e,n){for(var i=[],r=void 0!==n;(t=t[e])&&9!==t.nodeType;)if(1===t.nodeType){if(r&&m(t).is(n))break;i.push(t)}return i},k=function(t,e){for(var n=[];t;t=t.nextSibling)1===t.nodeType&&t!==e&&n.push(t);return n},S=m.expr.match.needsContext;function T(t,e){return t.nodeName&&t.nodeName.toLowerCase()===e.toLowerCase()}var D=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,O=/^.[^:#\[\.,]*$/;function A(t,e,n){return m.isFunction(e)?m.grep(t,function(t,i){return!!e.call(t,i,t)!==n}):e.nodeType?m.grep(t,function(t){return t===e!==n}):"string"!=typeof e?m.grep(t,function(t){return l.call(e,t)>-1!==n}):O.test(e)?m.filter(e,t,n):(e=m.filter(e,t),m.grep(t,function(t){return l.call(e,t)>-1!==n&&1===t.nodeType}))}m.filter=function(t,e,n){var i=e[0];return n&&(t=":not("+t+")"),1===e.length&&1===i.nodeType?m.find.matchesSelector(i,t)?[i]:[]:m.find.matches(t,m.grep(e,function(t){return 1===t.nodeType}))},m.fn.extend({find:function(t){var e,n,i=this.length,r=this;if("string"!=typeof t)return this.pushStack(m(t).filter(function(){for(e=0;e1?m.uniqueSort(n):n},filter:function(t){return this.pushStack(A(this,t||[],!1))},not:function(t){return this.pushStack(A(this,t||[],!0))},is:function(t){return!!A(this,"string"==typeof t&&S.test(t)?m(t):t||[],!1).length}});var M,E=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(m.fn.init=function(t,e,n){var r,o;if(!t)return this;if(n=n||M,"string"==typeof t){if(!(r="<"===t[0]&&">"===t[t.length-1]&&t.length>=3?[null,t,null]:E.exec(t))||!r[1]&&e)return!e||e.jquery?(e||n).find(t):this.constructor(e).find(t);if(r[1]){if(e=e instanceof m?e[0]:e,m.merge(this,m.parseHTML(r[1],e&&e.nodeType?e.ownerDocument||e:i,!0)),D.test(r[1])&&m.isPlainObject(e))for(r in e)m.isFunction(this[r])?this[r](e[r]):this.attr(r,e[r]);return this}return(o=i.getElementById(r[2]))&&(this[0]=o,this.length=1),this}return t.nodeType?(this[0]=t,this.length=1,this):m.isFunction(t)?void 0!==n.ready?n.ready(t):t(m):m.makeArray(t,this)}).prototype=m.fn,M=m(i);var I=/^(?:parents|prev(?:Until|All))/,P={children:!0,contents:!0,next:!0,prev:!0};function N(t,e){for(;(t=t[e])&&1!==t.nodeType;);return t}m.fn.extend({has:function(t){var e=m(t,this),n=e.length;return this.filter(function(){for(var t=0;t-1:1===n.nodeType&&m.find.matchesSelector(n,t))){o.push(n);break}return this.pushStack(o.length>1?m.uniqueSort(o):o)},index:function(t){return t?"string"==typeof t?l.call(m(t),this[0]):l.call(this,t.jquery?t[0]:t):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(t,e){return this.pushStack(m.uniqueSort(m.merge(this.get(),m(t,e))))},addBack:function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}}),m.each({parent:function(t){var e=t.parentNode;return e&&11!==e.nodeType?e:null},parents:function(t){return C(t,"parentNode")},parentsUntil:function(t,e,n){return C(t,"parentNode",n)},next:function(t){return N(t,"nextSibling")},prev:function(t){return N(t,"previousSibling")},nextAll:function(t){return C(t,"nextSibling")},prevAll:function(t){return C(t,"previousSibling")},nextUntil:function(t,e,n){return C(t,"nextSibling",n)},prevUntil:function(t,e,n){return C(t,"previousSibling",n)},siblings:function(t){return k((t.parentNode||{}).firstChild,t)},children:function(t){return k(t.firstChild)},contents:function(t){return T(t,"iframe")?t.contentDocument:(T(t,"template")&&(t=t.content||t),m.merge([],t.childNodes))}},function(t,e){m.fn[t]=function(n,i){var r=m.map(this,e,n);return"Until"!==t.slice(-5)&&(i=n),i&&"string"==typeof i&&(r=m.filter(i,r)),this.length>1&&(P[t]||m.uniqueSort(r),I.test(t)&&r.reverse()),this.pushStack(r)}});var F=/[^\x20\t\r\n\f]+/g;function L(t){return t}function R(t){throw t}function $(t,e,n,i){var r;try{t&&m.isFunction(r=t.promise)?r.call(t).done(e).fail(n):t&&m.isFunction(r=t.then)?r.call(t,e,n):e.apply(void 0,[t].slice(i))}catch(t){n.apply(void 0,[t])}}m.Callbacks=function(t){t="string"==typeof t?function(t){var e={};return m.each(t.match(F)||[],function(t,n){e[n]=!0}),e}(t):m.extend({},t);var e,n,i,r,o=[],a=[],s=-1,l=function(){for(r=r||t.once,i=e=!0;a.length;s=-1)for(n=a.shift();++s-1;)o.splice(n,1),n<=s&&s--}),this},has:function(t){return t?m.inArray(t,o)>-1:o.length>0},empty:function(){return o&&(o=[]),this},disable:function(){return r=a=[],o=n="",this},disabled:function(){return!o},lock:function(){return r=a=[],n||e||(o=n=""),this},locked:function(){return!!r},fireWith:function(t,n){return r||(n=[t,(n=n||[]).slice?n.slice():n],a.push(n),e||l()),this},fire:function(){return u.fireWith(this,arguments),this},fired:function(){return!!i}};return u},m.extend({Deferred:function(e){var n=[["notify","progress",m.Callbacks("memory"),m.Callbacks("memory"),2],["resolve","done",m.Callbacks("once memory"),m.Callbacks("once memory"),0,"resolved"],["reject","fail",m.Callbacks("once memory"),m.Callbacks("once memory"),1,"rejected"]],i="pending",r={state:function(){return i},always:function(){return o.done(arguments).fail(arguments),this},catch:function(t){return r.then(null,t)},pipe:function(){var t=arguments;return m.Deferred(function(e){m.each(n,function(n,i){var r=m.isFunction(t[i[4]])&&t[i[4]];o[i[1]](function(){var t=r&&r.apply(this,arguments);t&&m.isFunction(t.promise)?t.promise().progress(e.notify).done(e.resolve).fail(e.reject):e[i[0]+"With"](this,r?[t]:arguments)})}),t=null}).promise()},then:function(e,i,r){var o=0;function a(e,n,i,r){return function(){var s=this,l=arguments,u=function(){var t,u;if(!(e=o&&(i!==R&&(s=void 0,l=[t]),n.rejectWith(s,l))}};e?c():(m.Deferred.getStackHook&&(c.stackTrace=m.Deferred.getStackHook()),t.setTimeout(c))}}return m.Deferred(function(t){n[0][3].add(a(0,t,m.isFunction(r)?r:L,t.notifyWith)),n[1][3].add(a(0,t,m.isFunction(e)?e:L)),n[2][3].add(a(0,t,m.isFunction(i)?i:R))}).promise()},promise:function(t){return null!=t?m.extend(t,r):r}},o={};return m.each(n,function(t,e){var a=e[2],s=e[5];r[e[1]]=a.add,s&&a.add(function(){i=s},n[3-t][2].disable,n[0][2].lock),a.add(e[3].fire),o[e[0]]=function(){return o[e[0]+"With"](this===o?void 0:this,arguments),this},o[e[0]+"With"]=a.fireWith}),r.promise(o),e&&e.call(o,o),o},when:function(t){var e=arguments.length,n=e,i=Array(n),r=o.call(arguments),a=m.Deferred(),s=function(t){return function(n){i[t]=this,r[t]=arguments.length>1?o.call(arguments):n,--e||a.resolveWith(i,r)}};if(e<=1&&($(t,a.done(s(n)).resolve,a.reject,!e),"pending"===a.state()||m.isFunction(r[n]&&r[n].then)))return a.then();for(;n--;)$(r[n],s(n),a.reject);return a.promise()}});var j=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;m.Deferred.exceptionHook=function(e,n){t.console&&t.console.warn&&e&&j.test(e.name)&&t.console.warn("jQuery.Deferred exception: "+e.message,e.stack,n)},m.readyException=function(e){t.setTimeout(function(){throw e})};var H=m.Deferred();function W(){i.removeEventListener("DOMContentLoaded",W),t.removeEventListener("load",W),m.ready()}m.fn.ready=function(t){return H.then(t).catch(function(t){m.readyException(t)}),this},m.extend({isReady:!1,readyWait:1,ready:function(t){(!0===t?--m.readyWait:m.isReady)||(m.isReady=!0,!0!==t&&--m.readyWait>0||H.resolveWith(i,[m]))}}),m.ready.then=H.then,"complete"===i.readyState||"loading"!==i.readyState&&!i.documentElement.doScroll?t.setTimeout(m.ready):(i.addEventListener("DOMContentLoaded",W),t.addEventListener("load",W));var B=function(t,e,n,i,r,o,a){var s=0,l=t.length,u=null==n;if("object"===m.type(n))for(s in r=!0,n)B(t,e,s,n[s],!0,o,a);else if(void 0!==i&&(r=!0,m.isFunction(i)||(a=!0),u&&(a?(e.call(t,i),e=null):(u=e,e=function(t,e,n){return u.call(m(t),n)})),e))for(;s1,null,!0)},removeData:function(t){return this.each(function(){Y.remove(this,t)})}}),m.extend({queue:function(t,e,n){var i;if(t)return e=(e||"fx")+"queue",i=V.get(t,e),n&&(!i||Array.isArray(n)?i=V.access(t,e,m.makeArray(n)):i.push(n)),i||[]},dequeue:function(t,e){e=e||"fx";var n=m.queue(t,e),i=n.length,r=n.shift(),o=m._queueHooks(t,e);"inprogress"===r&&(r=n.shift(),i--),r&&("fx"===e&&n.unshift("inprogress"),delete o.stop,r.call(t,function(){m.dequeue(t,e)},o)),!i&&o&&o.empty.fire()},_queueHooks:function(t,e){var n=e+"queueHooks";return V.get(t,n)||V.access(t,n,{empty:m.Callbacks("once memory").add(function(){V.remove(t,[e+"queue",n])})})}}),m.fn.extend({queue:function(t,e){var n=2;return"string"!=typeof t&&(e=t,t="fx",n--),arguments.length\x20\t\r\n\f]+)/i,st=/^$|\/(?:java|ecma)script/i,lt={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ut(t,e){var n;return n=void 0!==t.getElementsByTagName?t.getElementsByTagName(e||"*"):void 0!==t.querySelectorAll?t.querySelectorAll(e||"*"):[],void 0===e||e&&T(t,e)?m.merge([t],n):n}function ct(t,e){for(var n=0,i=t.length;n-1)r&&r.push(o);else if(u=m.contains(o.ownerDocument,o),a=ut(d.appendChild(o),"script"),u&&ct(a),n)for(c=0;o=a[c++];)st.test(o.type||"")&&n.push(o);return d}dt=i.createDocumentFragment().appendChild(i.createElement("div")),(ht=i.createElement("input")).setAttribute("type","radio"),ht.setAttribute("checked","checked"),ht.setAttribute("name","t"),dt.appendChild(ht),p.checkClone=dt.cloneNode(!0).cloneNode(!0).lastChild.checked,dt.innerHTML="",p.noCloneChecked=!!dt.cloneNode(!0).lastChild.defaultValue;var gt=i.documentElement,mt=/^key/,vt=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,yt=/^([^.]*)(?:\.(.+)|)/;function bt(){return!0}function _t(){return!1}function xt(){try{return i.activeElement}catch(t){}}function wt(t,e,n,i,r,o){var a,s;if("object"==typeof e){for(s in"string"!=typeof n&&(i=i||n,n=void 0),e)wt(t,s,n,i,e[s],o);return t}if(null==i&&null==r?(r=n,i=n=void 0):null==r&&("string"==typeof n?(r=i,i=void 0):(r=i,i=n,n=void 0)),!1===r)r=_t;else if(!r)return t;return 1===o&&(a=r,(r=function(t){return m().off(t),a.apply(this,arguments)}).guid=a.guid||(a.guid=m.guid++)),t.each(function(){m.event.add(this,e,r,i,n)})}m.event={global:{},add:function(t,e,n,i,r){var o,a,s,l,u,c,d,h,f,p,g,v=V.get(t);if(v)for(n.handler&&(n=(o=n).handler,r=o.selector),r&&m.find.matchesSelector(gt,r),n.guid||(n.guid=m.guid++),(l=v.events)||(l=v.events={}),(a=v.handle)||(a=v.handle=function(e){return void 0!==m&&m.event.triggered!==e.type?m.event.dispatch.apply(t,arguments):void 0}),u=(e=(e||"").match(F)||[""]).length;u--;)f=g=(s=yt.exec(e[u])||[])[1],p=(s[2]||"").split(".").sort(),f&&(d=m.event.special[f]||{},f=(r?d.delegateType:d.bindType)||f,d=m.event.special[f]||{},c=m.extend({type:f,origType:g,data:i,handler:n,guid:n.guid,selector:r,needsContext:r&&m.expr.match.needsContext.test(r),namespace:p.join(".")},o),(h=l[f])||((h=l[f]=[]).delegateCount=0,d.setup&&!1!==d.setup.call(t,i,p,a)||t.addEventListener&&t.addEventListener(f,a)),d.add&&(d.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),r?h.splice(h.delegateCount++,0,c):h.push(c),m.event.global[f]=!0)},remove:function(t,e,n,i,r){var o,a,s,l,u,c,d,h,f,p,g,v=V.hasData(t)&&V.get(t);if(v&&(l=v.events)){for(u=(e=(e||"").match(F)||[""]).length;u--;)if(f=g=(s=yt.exec(e[u])||[])[1],p=(s[2]||"").split(".").sort(),f){for(d=m.event.special[f]||{},h=l[f=(i?d.delegateType:d.bindType)||f]||[],s=s[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=h.length;o--;)c=h[o],!r&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||i&&i!==c.selector&&("**"!==i||!c.selector)||(h.splice(o,1),c.selector&&h.delegateCount--,d.remove&&d.remove.call(t,c));a&&!h.length&&(d.teardown&&!1!==d.teardown.call(t,p,v.handle)||m.removeEvent(t,f,v.handle),delete l[f])}else for(f in l)m.event.remove(t,f+e[u],n,i,!0);m.isEmptyObject(l)&&V.remove(t,"handle events")}},dispatch:function(t){var e,n,i,r,o,a,s=m.event.fix(t),l=new Array(arguments.length),u=(V.get(this,"events")||{})[s.type]||[],c=m.event.special[s.type]||{};for(l[0]=s,e=1;e=1))for(;u!==this;u=u.parentNode||this)if(1===u.nodeType&&("click"!==t.type||!0!==u.disabled)){for(o=[],a={},n=0;n-1:m.find(r,this,null,[u]).length),a[r]&&o.push(i);o.length&&s.push({elem:u,handlers:o})}return u=this,l\x20\t\r\n\f]*)[^>]*)\/>/gi,kt=/\s*$/g;function Ot(t,e){return T(t,"table")&&T(11!==e.nodeType?e:e.firstChild,"tr")&&m(">tbody",t)[0]||t}function At(t){return t.type=(null!==t.getAttribute("type"))+"/"+t.type,t}function Mt(t){var e=Tt.exec(t.type);return e?t.type=e[1]:t.removeAttribute("type"),t}function Et(t,e){var n,i,r,o,a,s,l,u;if(1===e.nodeType){if(V.hasData(t)&&(o=V.access(t),a=V.set(e,o),u=o.events))for(r in delete a.handle,a.events={},u)for(n=0,i=u[r].length;n1&&"string"==typeof v&&!p.checkClone&&St.test(v))return t.each(function(r){var o=t.eq(r);y&&(e[0]=v.call(this,r,o.html())),It(o,e,n,i)});if(h&&(o=(r=pt(e,t[0].ownerDocument,!1,t,i)).firstChild,1===r.childNodes.length&&(r=o),o||i)){for(l=(s=m.map(ut(r,"script"),At)).length;d")},clone:function(t,e,n){var i,r,o,a,s,l,u,c=t.cloneNode(!0),d=m.contains(t.ownerDocument,t);if(!(p.noCloneChecked||1!==t.nodeType&&11!==t.nodeType||m.isXMLDoc(t)))for(a=ut(c),i=0,r=(o=ut(t)).length;i0&&ct(a,!d&&ut(t,"script")),c},cleanData:function(t){for(var e,n,i,r=m.event.special,o=0;void 0!==(n=t[o]);o++)if(z(n)){if(e=n[V.expando]){if(e.events)for(i in e.events)r[i]?m.event.remove(n,i):m.removeEvent(n,i,e.handle);n[V.expando]=void 0}n[Y.expando]&&(n[Y.expando]=void 0)}}}),m.fn.extend({detach:function(t){return Pt(this,t,!0)},remove:function(t){return Pt(this,t)},text:function(t){return B(this,function(t){return void 0===t?m.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=t)})},null,t,arguments.length)},append:function(){return It(this,arguments,function(t){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Ot(this,t).appendChild(t)})},prepend:function(){return It(this,arguments,function(t){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var e=Ot(this,t);e.insertBefore(t,e.firstChild)}})},before:function(){return It(this,arguments,function(t){this.parentNode&&this.parentNode.insertBefore(t,this)})},after:function(){return It(this,arguments,function(t){this.parentNode&&this.parentNode.insertBefore(t,this.nextSibling)})},empty:function(){for(var t,e=0;null!=(t=this[e]);e++)1===t.nodeType&&(m.cleanData(ut(t,!1)),t.textContent="");return this},clone:function(t,e){return t=null!=t&&t,e=null==e?t:e,this.map(function(){return m.clone(this,t,e)})},html:function(t){return B(this,function(t){var e=this[0]||{},n=0,i=this.length;if(void 0===t&&1===e.nodeType)return e.innerHTML;if("string"==typeof t&&!kt.test(t)&&!lt[(at.exec(t)||["",""])[1].toLowerCase()]){t=m.htmlPrefilter(t);try{for(;n1)}}),m.Tween=Gt,Gt.prototype={constructor:Gt,init:function(t,e,n,i,r,o){this.elem=t,this.prop=n,this.easing=r||m.easing._default,this.options=e,this.start=this.now=this.cur(),this.end=i,this.unit=o||(m.cssNumber[n]?"":"px")},cur:function(){var t=Gt.propHooks[this.prop];return t&&t.get?t.get(this):Gt.propHooks._default.get(this)},run:function(t){var e,n=Gt.propHooks[this.prop];return this.options.duration?this.pos=e=m.easing[this.easing](t,this.options.duration*t,0,1,this.options.duration):this.pos=e=t,this.now=(this.end-this.start)*e+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):Gt.propHooks._default.set(this),this}},Gt.prototype.init.prototype=Gt.prototype,Gt.propHooks={_default:{get:function(t){var e;return 1!==t.elem.nodeType||null!=t.elem[t.prop]&&null==t.elem.style[t.prop]?t.elem[t.prop]:(e=m.css(t.elem,t.prop,""))&&"auto"!==e?e:0},set:function(t){m.fx.step[t.prop]?m.fx.step[t.prop](t):1!==t.elem.nodeType||null==t.elem.style[m.cssProps[t.prop]]&&!m.cssHooks[t.prop]?t.elem[t.prop]=t.now:m.style(t.elem,t.prop,t.now+t.unit)}}},Gt.propHooks.scrollTop=Gt.propHooks.scrollLeft={set:function(t){t.elem.nodeType&&t.elem.parentNode&&(t.elem[t.prop]=t.now)}},m.easing={linear:function(t){return t},swing:function(t){return.5-Math.cos(t*Math.PI)/2},_default:"swing"},m.fx=Gt.prototype.init,m.fx.step={};var Qt,Xt,Jt=/^(?:toggle|show|hide)$/,Zt=/queueHooks$/;function te(){Xt&&(!1===i.hidden&&t.requestAnimationFrame?t.requestAnimationFrame(te):t.setTimeout(te,m.fx.interval),m.fx.tick())}function ee(){return t.setTimeout(function(){Qt=void 0}),Qt=m.now()}function ne(t,e){var n,i=0,r={height:t};for(e=e?1:0;i<4;i+=2-e)r["margin"+(n=J[i])]=r["padding"+n]=t;return e&&(r.opacity=r.width=t),r}function ie(t,e,n){for(var i,r=(re.tweeners[e]||[]).concat(re.tweeners["*"]),o=0,a=r.length;o1)},removeAttr:function(t){return this.each(function(){m.removeAttr(this,t)})}}),m.extend({attr:function(t,e,n){var i,r,o=t.nodeType;if(3!==o&&8!==o&&2!==o)return void 0===t.getAttribute?m.prop(t,e,n):(1===o&&m.isXMLDoc(t)||(r=m.attrHooks[e.toLowerCase()]||(m.expr.match.bool.test(e)?oe:void 0)),void 0!==n?null===n?void m.removeAttr(t,e):r&&"set"in r&&void 0!==(i=r.set(t,n,e))?i:(t.setAttribute(e,n+""),n):r&&"get"in r&&null!==(i=r.get(t,e))?i:null==(i=m.find.attr(t,e))?void 0:i)},attrHooks:{type:{set:function(t,e){if(!p.radioValue&&"radio"===e&&T(t,"input")){var n=t.value;return t.setAttribute("type",e),n&&(t.value=n),e}}}},removeAttr:function(t,e){var n,i=0,r=e&&e.match(F);if(r&&1===t.nodeType)for(;n=r[i++];)t.removeAttribute(n)}}),oe={set:function(t,e,n){return!1===e?m.removeAttr(t,n):t.setAttribute(n,n),n}},m.each(m.expr.match.bool.source.match(/\w+/g),function(t,e){var n=ae[e]||m.find.attr;ae[e]=function(t,e,i){var r,o,a=e.toLowerCase();return i||(o=ae[a],ae[a]=r,r=null!=n(t,e,i)?a:null,ae[a]=o),r}});var se=/^(?:input|select|textarea|button)$/i,le=/^(?:a|area)$/i;function ue(t){return(t.match(F)||[]).join(" ")}function ce(t){return t.getAttribute&&t.getAttribute("class")||""}m.fn.extend({prop:function(t,e){return B(this,m.prop,t,e,arguments.length>1)},removeProp:function(t){return this.each(function(){delete this[m.propFix[t]||t]})}}),m.extend({prop:function(t,e,n){var i,r,o=t.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&m.isXMLDoc(t)||(e=m.propFix[e]||e,r=m.propHooks[e]),void 0!==n?r&&"set"in r&&void 0!==(i=r.set(t,n,e))?i:t[e]=n:r&&"get"in r&&null!==(i=r.get(t,e))?i:t[e]},propHooks:{tabIndex:{get:function(t){var e=m.find.attr(t,"tabindex");return e?parseInt(e,10):se.test(t.nodeName)||le.test(t.nodeName)&&t.href?0:-1}}},propFix:{for:"htmlFor",class:"className"}}),p.optSelected||(m.propHooks.selected={get:function(t){var e=t.parentNode;return e&&e.parentNode&&e.parentNode.selectedIndex,null},set:function(t){var e=t.parentNode;e&&(e.selectedIndex,e.parentNode&&e.parentNode.selectedIndex)}}),m.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){m.propFix[this.toLowerCase()]=this}),m.fn.extend({addClass:function(t){var e,n,i,r,o,a,s,l=0;if(m.isFunction(t))return this.each(function(e){m(this).addClass(t.call(this,e,ce(this)))});if("string"==typeof t&&t)for(e=t.match(F)||[];n=this[l++];)if(r=ce(n),i=1===n.nodeType&&" "+ue(r)+" "){for(a=0;o=e[a++];)i.indexOf(" "+o+" ")<0&&(i+=o+" ");r!==(s=ue(i))&&n.setAttribute("class",s)}return this},removeClass:function(t){var e,n,i,r,o,a,s,l=0;if(m.isFunction(t))return this.each(function(e){m(this).removeClass(t.call(this,e,ce(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof t&&t)for(e=t.match(F)||[];n=this[l++];)if(r=ce(n),i=1===n.nodeType&&" "+ue(r)+" "){for(a=0;o=e[a++];)for(;i.indexOf(" "+o+" ")>-1;)i=i.replace(" "+o+" "," ");r!==(s=ue(i))&&n.setAttribute("class",s)}return this},toggleClass:function(t,e){var n=typeof t;return"boolean"==typeof e&&"string"===n?e?this.addClass(t):this.removeClass(t):m.isFunction(t)?this.each(function(n){m(this).toggleClass(t.call(this,n,ce(this),e),e)}):this.each(function(){var e,i,r,o;if("string"===n)for(i=0,r=m(this),o=t.match(F)||[];e=o[i++];)r.hasClass(e)?r.removeClass(e):r.addClass(e);else void 0!==t&&"boolean"!==n||((e=ce(this))&&V.set(this,"__className__",e),this.setAttribute&&this.setAttribute("class",e||!1===t?"":V.get(this,"__className__")||""))})},hasClass:function(t){var e,n,i=0;for(e=" "+t+" ";n=this[i++];)if(1===n.nodeType&&(" "+ue(ce(n))+" ").indexOf(e)>-1)return!0;return!1}});var de=/\r/g;m.fn.extend({val:function(t){var e,n,i,r=this[0];return arguments.length?(i=m.isFunction(t),this.each(function(n){var r;1===this.nodeType&&(null==(r=i?t.call(this,n,m(this).val()):t)?r="":"number"==typeof r?r+="":Array.isArray(r)&&(r=m.map(r,function(t){return null==t?"":t+""})),(e=m.valHooks[this.type]||m.valHooks[this.nodeName.toLowerCase()])&&"set"in e&&void 0!==e.set(this,r,"value")||(this.value=r))})):r?(e=m.valHooks[r.type]||m.valHooks[r.nodeName.toLowerCase()])&&"get"in e&&void 0!==(n=e.get(r,"value"))?n:"string"==typeof(n=r.value)?n.replace(de,""):null==n?"":n:void 0}}),m.extend({valHooks:{option:{get:function(t){var e=m.find.attr(t,"value");return null!=e?e:ue(m.text(t))}},select:{get:function(t){var e,n,i,r=t.options,o=t.selectedIndex,a="select-one"===t.type,s=a?null:[],l=a?o+1:r.length;for(i=o<0?l:a?o:0;i-1)&&(n=!0);return n||(t.selectedIndex=-1),o}}}}),m.each(["radio","checkbox"],function(){m.valHooks[this]={set:function(t,e){if(Array.isArray(e))return t.checked=m.inArray(m(t).val(),e)>-1}},p.checkOn||(m.valHooks[this].get=function(t){return null===t.getAttribute("value")?"on":t.value})});var he=/^(?:focusinfocus|focusoutblur)$/;m.extend(m.event,{trigger:function(e,n,r,o){var a,s,l,u,c,h,f,p=[r||i],g=d.call(e,"type")?e.type:e,v=d.call(e,"namespace")?e.namespace.split("."):[];if(s=l=r=r||i,3!==r.nodeType&&8!==r.nodeType&&!he.test(g+m.event.triggered)&&(g.indexOf(".")>-1&&(g=(v=g.split(".")).shift(),v.sort()),c=g.indexOf(":")<0&&"on"+g,(e=e[m.expando]?e:new m.Event(g,"object"==typeof e&&e)).isTrigger=o?2:3,e.namespace=v.join("."),e.rnamespace=e.namespace?new RegExp("(^|\\.)"+v.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,e.result=void 0,e.target||(e.target=r),n=null==n?[e]:m.makeArray(n,[e]),f=m.event.special[g]||{},o||!f.trigger||!1!==f.trigger.apply(r,n))){if(!o&&!f.noBubble&&!m.isWindow(r)){for(u=f.delegateType||g,he.test(u+g)||(s=s.parentNode);s;s=s.parentNode)p.push(s),l=s;l===(r.ownerDocument||i)&&p.push(l.defaultView||l.parentWindow||t)}for(a=0;(s=p[a++])&&!e.isPropagationStopped();)e.type=a>1?u:f.bindType||g,(h=(V.get(s,"events")||{})[e.type]&&V.get(s,"handle"))&&h.apply(s,n),(h=c&&s[c])&&h.apply&&z(s)&&(e.result=h.apply(s,n),!1===e.result&&e.preventDefault());return e.type=g,o||e.isDefaultPrevented()||f._default&&!1!==f._default.apply(p.pop(),n)||!z(r)||c&&m.isFunction(r[g])&&!m.isWindow(r)&&((l=r[c])&&(r[c]=null),m.event.triggered=g,r[g](),m.event.triggered=void 0,l&&(r[c]=l)),e.result}},simulate:function(t,e,n){var i=m.extend(new m.Event,n,{type:t,isSimulated:!0});m.event.trigger(i,null,e)}}),m.fn.extend({trigger:function(t,e){return this.each(function(){m.event.trigger(t,e,this)})},triggerHandler:function(t,e){var n=this[0];if(n)return m.event.trigger(t,e,n,!0)}}),m.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(t,e){m.fn[e]=function(t,n){return arguments.length>0?this.on(e,null,t,n):this.trigger(e)}}),m.fn.extend({hover:function(t,e){return this.mouseenter(t).mouseleave(e||t)}}),p.focusin="onfocusin"in t,p.focusin||m.each({focus:"focusin",blur:"focusout"},function(t,e){var n=function(t){m.event.simulate(e,t.target,m.event.fix(t))};m.event.special[e]={setup:function(){var i=this.ownerDocument||this,r=V.access(i,e);r||i.addEventListener(t,n,!0),V.access(i,e,(r||0)+1)},teardown:function(){var i=this.ownerDocument||this,r=V.access(i,e)-1;r?V.access(i,e,r):(i.removeEventListener(t,n,!0),V.remove(i,e))}}});var fe=t.location,pe=m.now(),ge=/\?/;m.parseXML=function(e){var n;if(!e||"string"!=typeof e)return null;try{n=(new t.DOMParser).parseFromString(e,"text/xml")}catch(t){n=void 0}return n&&!n.getElementsByTagName("parsererror").length||m.error("Invalid XML: "+e),n};var me=/\[\]$/,ve=/\r?\n/g,ye=/^(?:submit|button|image|reset|file)$/i,be=/^(?:input|select|textarea|keygen)/i;function _e(t,e,n,i){var r;if(Array.isArray(e))m.each(e,function(e,r){n||me.test(t)?i(t,r):_e(t+"["+("object"==typeof r&&null!=r?e:"")+"]",r,n,i)});else if(n||"object"!==m.type(e))i(t,e);else for(r in e)_e(t+"["+r+"]",e[r],n,i)}m.param=function(t,e){var n,i=[],r=function(t,e){var n=m.isFunction(e)?e():e;i[i.length]=encodeURIComponent(t)+"="+encodeURIComponent(null==n?"":n)};if(Array.isArray(t)||t.jquery&&!m.isPlainObject(t))m.each(t,function(){r(this.name,this.value)});else for(n in t)_e(n,t[n],e,r);return i.join("&")},m.fn.extend({serialize:function(){return m.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var t=m.prop(this,"elements");return t?m.makeArray(t):this}).filter(function(){var t=this.type;return this.name&&!m(this).is(":disabled")&&be.test(this.nodeName)&&!ye.test(t)&&(this.checked||!ot.test(t))}).map(function(t,e){var n=m(this).val();return null==n?null:Array.isArray(n)?m.map(n,function(t){return{name:e.name,value:t.replace(ve,"\r\n")}}):{name:e.name,value:n.replace(ve,"\r\n")}}).get()}});var xe=/%20/g,we=/#.*$/,Ce=/([?&])_=[^&]*/,ke=/^(.*?):[ \t]*([^\r\n]*)$/gm,Se=/^(?:GET|HEAD)$/,Te=/^\/\//,De={},Oe={},Ae="*/".concat("*"),Me=i.createElement("a");function Ee(t){return function(e,n){"string"!=typeof e&&(n=e,e="*");var i,r=0,o=e.toLowerCase().match(F)||[];if(m.isFunction(n))for(;i=o[r++];)"+"===i[0]?(i=i.slice(1)||"*",(t[i]=t[i]||[]).unshift(n)):(t[i]=t[i]||[]).push(n)}}function Ie(t,e,n,i){var r={},o=t===Oe;function a(s){var l;return r[s]=!0,m.each(t[s]||[],function(t,s){var u=s(e,n,i);return"string"!=typeof u||o||r[u]?o?!(l=u):void 0:(e.dataTypes.unshift(u),a(u),!1)}),l}return a(e.dataTypes[0])||!r["*"]&&a("*")}function Pe(t,e){var n,i,r=m.ajaxSettings.flatOptions||{};for(n in e)void 0!==e[n]&&((r[n]?t:i||(i={}))[n]=e[n]);return i&&m.extend(!0,t,i),t}Me.href=fe.href,m.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:fe.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(fe.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Ae,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":m.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(t,e){return e?Pe(Pe(t,m.ajaxSettings),e):Pe(m.ajaxSettings,t)},ajaxPrefilter:Ee(De),ajaxTransport:Ee(Oe),ajax:function(e,n){"object"==typeof e&&(n=e,e=void 0),n=n||{};var r,o,a,s,l,u,c,d,h,f,p=m.ajaxSetup({},n),g=p.context||p,v=p.context&&(g.nodeType||g.jquery)?m(g):m.event,y=m.Deferred(),b=m.Callbacks("once memory"),_=p.statusCode||{},x={},w={},C="canceled",k={readyState:0,getResponseHeader:function(t){var e;if(c){if(!s)for(s={};e=ke.exec(a);)s[e[1].toLowerCase()]=e[2];e=s[t.toLowerCase()]}return null==e?null:e},getAllResponseHeaders:function(){return c?a:null},setRequestHeader:function(t,e){return null==c&&(t=w[t.toLowerCase()]=w[t.toLowerCase()]||t,x[t]=e),this},overrideMimeType:function(t){return null==c&&(p.mimeType=t),this},statusCode:function(t){var e;if(t)if(c)k.always(t[k.status]);else for(e in t)_[e]=[_[e],t[e]];return this},abort:function(t){var e=t||C;return r&&r.abort(e),S(0,e),this}};if(y.promise(k),p.url=((e||p.url||fe.href)+"").replace(Te,fe.protocol+"//"),p.type=n.method||n.type||p.method||p.type,p.dataTypes=(p.dataType||"*").toLowerCase().match(F)||[""],null==p.crossDomain){u=i.createElement("a");try{u.href=p.url,u.href=u.href,p.crossDomain=Me.protocol+"//"+Me.host!=u.protocol+"//"+u.host}catch(t){p.crossDomain=!0}}if(p.data&&p.processData&&"string"!=typeof p.data&&(p.data=m.param(p.data,p.traditional)),Ie(De,p,n,k),c)return k;for(h in(d=m.event&&p.global)&&0==m.active++&&m.event.trigger("ajaxStart"),p.type=p.type.toUpperCase(),p.hasContent=!Se.test(p.type),o=p.url.replace(we,""),p.hasContent?p.data&&p.processData&&0===(p.contentType||"").indexOf("application/x-www-form-urlencoded")&&(p.data=p.data.replace(xe,"+")):(f=p.url.slice(o.length),p.data&&(o+=(ge.test(o)?"&":"?")+p.data,delete p.data),!1===p.cache&&(o=o.replace(Ce,"$1"),f=(ge.test(o)?"&":"?")+"_="+pe+++f),p.url=o+f),p.ifModified&&(m.lastModified[o]&&k.setRequestHeader("If-Modified-Since",m.lastModified[o]),m.etag[o]&&k.setRequestHeader("If-None-Match",m.etag[o])),(p.data&&p.hasContent&&!1!==p.contentType||n.contentType)&&k.setRequestHeader("Content-Type",p.contentType),k.setRequestHeader("Accept",p.dataTypes[0]&&p.accepts[p.dataTypes[0]]?p.accepts[p.dataTypes[0]]+("*"!==p.dataTypes[0]?", "+Ae+"; q=0.01":""):p.accepts["*"]),p.headers)k.setRequestHeader(h,p.headers[h]);if(p.beforeSend&&(!1===p.beforeSend.call(g,k,p)||c))return k.abort();if(C="abort",b.add(p.complete),k.done(p.success),k.fail(p.error),r=Ie(Oe,p,n,k)){if(k.readyState=1,d&&v.trigger("ajaxSend",[k,p]),c)return k;p.async&&p.timeout>0&&(l=t.setTimeout(function(){k.abort("timeout")},p.timeout));try{c=!1,r.send(x,S)}catch(t){if(c)throw t;S(-1,t)}}else S(-1,"No Transport");function S(e,n,i,s){var u,h,f,x,w,C=n;c||(c=!0,l&&t.clearTimeout(l),r=void 0,a=s||"",k.readyState=e>0?4:0,u=e>=200&&e<300||304===e,i&&(x=function(t,e,n){for(var i,r,o,a,s=t.contents,l=t.dataTypes;"*"===l[0];)l.shift(),void 0===i&&(i=t.mimeType||e.getResponseHeader("Content-Type"));if(i)for(r in s)if(s[r]&&s[r].test(i)){l.unshift(r);break}if(l[0]in n)o=l[0];else{for(r in n){if(!l[0]||t.converters[r+" "+l[0]]){o=r;break}a||(a=r)}o=o||a}if(o)return o!==l[0]&&l.unshift(o),n[o]}(p,k,i)),x=function(t,e,n,i){var r,o,a,s,l,u={},c=t.dataTypes.slice();if(c[1])for(a in t.converters)u[a.toLowerCase()]=t.converters[a];for(o=c.shift();o;)if(t.responseFields[o]&&(n[t.responseFields[o]]=e),!l&&i&&t.dataFilter&&(e=t.dataFilter(e,t.dataType)),l=o,o=c.shift())if("*"===o)o=l;else if("*"!==l&&l!==o){if(!(a=u[l+" "+o]||u["* "+o]))for(r in u)if((s=r.split(" "))[1]===o&&(a=u[l+" "+s[0]]||u["* "+s[0]])){!0===a?a=u[r]:!0!==u[r]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&t.throws)e=a(e);else try{e=a(e)}catch(t){return{state:"parsererror",error:a?t:"No conversion from "+l+" to "+o}}}return{state:"success",data:e}}(p,x,k,u),u?(p.ifModified&&((w=k.getResponseHeader("Last-Modified"))&&(m.lastModified[o]=w),(w=k.getResponseHeader("etag"))&&(m.etag[o]=w)),204===e||"HEAD"===p.type?C="nocontent":304===e?C="notmodified":(C=x.state,h=x.data,u=!(f=x.error))):(f=C,!e&&C||(C="error",e<0&&(e=0))),k.status=e,k.statusText=(n||C)+"",u?y.resolveWith(g,[h,C,k]):y.rejectWith(g,[k,C,f]),k.statusCode(_),_=void 0,d&&v.trigger(u?"ajaxSuccess":"ajaxError",[k,p,u?h:f]),b.fireWith(g,[k,C]),d&&(v.trigger("ajaxComplete",[k,p]),--m.active||m.event.trigger("ajaxStop")))}return k},getJSON:function(t,e,n){return m.get(t,e,n,"json")},getScript:function(t,e){return m.get(t,void 0,e,"script")}}),m.each(["get","post"],function(t,e){m[e]=function(t,n,i,r){return m.isFunction(n)&&(r=r||i,i=n,n=void 0),m.ajax(m.extend({url:t,type:e,dataType:r,data:n,success:i},m.isPlainObject(t)&&t))}}),m._evalUrl=function(t){return m.ajax({url:t,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,throws:!0})},m.fn.extend({wrapAll:function(t){var e;return this[0]&&(m.isFunction(t)&&(t=t.call(this[0])),e=m(t,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&e.insertBefore(this[0]),e.map(function(){for(var t=this;t.firstElementChild;)t=t.firstElementChild;return t}).append(this)),this},wrapInner:function(t){return m.isFunction(t)?this.each(function(e){m(this).wrapInner(t.call(this,e))}):this.each(function(){var e=m(this),n=e.contents();n.length?n.wrapAll(t):e.append(t)})},wrap:function(t){var e=m.isFunction(t);return this.each(function(n){m(this).wrapAll(e?t.call(this,n):t)})},unwrap:function(t){return this.parent(t).not("body").each(function(){m(this).replaceWith(this.childNodes)}),this}}),m.expr.pseudos.hidden=function(t){return!m.expr.pseudos.visible(t)},m.expr.pseudos.visible=function(t){return!!(t.offsetWidth||t.offsetHeight||t.getClientRects().length)},m.ajaxSettings.xhr=function(){try{return new t.XMLHttpRequest}catch(t){}};var Ne={0:200,1223:204},Fe=m.ajaxSettings.xhr();p.cors=!!Fe&&"withCredentials"in Fe,p.ajax=Fe=!!Fe,m.ajaxTransport(function(e){var n,i;if(p.cors||Fe&&!e.crossDomain)return{send:function(r,o){var a,s=e.xhr();if(s.open(e.type,e.url,e.async,e.username,e.password),e.xhrFields)for(a in e.xhrFields)s[a]=e.xhrFields[a];for(a in e.mimeType&&s.overrideMimeType&&s.overrideMimeType(e.mimeType),e.crossDomain||r["X-Requested-With"]||(r["X-Requested-With"]="XMLHttpRequest"),r)s.setRequestHeader(a,r[a]);n=function(t){return function(){n&&(n=i=s.onload=s.onerror=s.onabort=s.onreadystatechange=null,"abort"===t?s.abort():"error"===t?"number"!=typeof s.status?o(0,"error"):o(s.status,s.statusText):o(Ne[s.status]||s.status,s.statusText,"text"!==(s.responseType||"text")||"string"!=typeof s.responseText?{binary:s.response}:{text:s.responseText},s.getAllResponseHeaders()))}},s.onload=n(),i=s.onerror=n("error"),void 0!==s.onabort?s.onabort=i:s.onreadystatechange=function(){4===s.readyState&&t.setTimeout(function(){n&&i()})},n=n("abort");try{s.send(e.hasContent&&e.data||null)}catch(t){if(n)throw t}},abort:function(){n&&n()}}}),m.ajaxPrefilter(function(t){t.crossDomain&&(t.contents.script=!1)}),m.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(t){return m.globalEval(t),t}}}),m.ajaxPrefilter("script",function(t){void 0===t.cache&&(t.cache=!1),t.crossDomain&&(t.type="GET")}),m.ajaxTransport("script",function(t){var e,n;if(t.crossDomain)return{send:function(r,o){e=m(" +@endpush \ No newline at end of file diff --git a/resources/views/themes/base/gallery/user_activity_feed.blade.php b/resources/views/themes/base/gallery/user_activity_feed.blade.php new file mode 100644 index 0000000..175b69d --- /dev/null +++ b/resources/views/themes/base/gallery/user_activity_feed.blade.php @@ -0,0 +1,57 @@ +@extends(Theme::viewName('layout')) +@section('title', trans('gallery.user_activity_feed.title')) + +@section('breadcrumb') + + +@endsection + +@section('content') +
+
+
+
+

+ @lang('global.please_wait') +

+

+ @lang('global.please_wait') +

+
+
+
+
+
+ +
+
+
+ +
+
+
+
+
+

@lang('gallery.user_activity_feed.no_activity_p1')

+

@lang('gallery.user_activity_feed.no_activity_p2')

+

@lang('gallery.user_activity_feed.no_activity_p3', [ + 'l_explore_start' => sprintf('', route('exploreUsers')), + 'l_explore_end' => '' + ])

+

@lang('gallery.user_activity_feed.explore_photographers_link')

+
+
+
+
+@endsection + +@push('scripts') + +@endpush \ No newline at end of file diff --git a/resources/views/themes/base/gallery/user_profile.blade.php b/resources/views/themes/base/gallery/user_profile.blade.php index 86133e1..fe50f7b 100644 --- a/resources/views/themes/base/gallery/user_profile.blade.php +++ b/resources/views/themes/base/gallery/user_profile.blade.php @@ -7,13 +7,20 @@ @endsection @section('content') -
+
+ @if ($can_follow) + @if ($is_following) + @lang('gallery.user_profile.following_button') + @else + @lang('gallery.user_profile.follow_button') + @endif + @endif

{{ $user->name }}

@if (!empty($user->profile_alias))

{{ $user->profile_alias }}

@@ -21,7 +28,20 @@
-
+ + +
@if (count($albums) == 0)

@lang('gallery.user_profile.no_albums_p1')

@@ -87,5 +107,56 @@ @endif
+ +
+
+
+

+ @lang('global.please_wait') +

+

+ @lang('global.please_wait') +

+
+
+
+
+
+ +
+
+
+ +
+
+
+
+
+

@lang('gallery.user_profile.no_feed_activity_p1')

+

@lang('gallery.user_profile.no_feed_activity_p2', ['user_name' => $user->name])

+
+
+
-@endsection \ No newline at end of file +@endsection + +@push('scripts') + +@endpush \ No newline at end of file diff --git a/resources/views/themes/base/partials/navbar.blade.php b/resources/views/themes/base/partials/navbar.blade.php index 369ba68..1c5bf70 100644 --- a/resources/views/themes/base/partials/navbar.blade.php +++ b/resources/views/themes/base/partials/navbar.blade.php @@ -6,6 +6,13 @@