Merge user feeds & followers #118
@ -13,6 +13,7 @@ use App\User;
|
|||||||
use App\UserActivity;
|
use App\UserActivity;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\App;
|
use Illuminate\Support\Facades\App;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Mail;
|
use Illuminate\Support\Facades\Mail;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
@ -39,6 +40,25 @@ class UserController extends Controller
|
|||||||
return redirect(route('userSettings'));
|
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)
|
public function resetEmailChangeState(Request $request)
|
||||||
{
|
{
|
||||||
$user = $this->getUser();
|
$user = $this->getUser();
|
||||||
@ -128,12 +148,24 @@ class UserController extends Controller
|
|||||||
|
|
||||||
$daysInMonth = $this->getDaysInMonths();
|
$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', [
|
return Theme::render('gallery.user_profile', [
|
||||||
'active_tab' => $request->get('tab'),
|
'active_tab' => $request->get('tab'),
|
||||||
'activity_taken' => $this->constructActivityGrid($activity['taken']),
|
'activity_taken' => $this->constructActivityGrid($activity['taken']),
|
||||||
'activity_uploaded' => $this->constructActivityGrid($activity['uploaded']),
|
'activity_uploaded' => $this->constructActivityGrid($activity['uploaded']),
|
||||||
'albums' => $albums,
|
'albums' => $albums,
|
||||||
'cameras' => $cameras,
|
'cameras' => $cameras,
|
||||||
|
'can_follow' => $canFollow,
|
||||||
|
'is_following' => $isFollowing,
|
||||||
'month_days' => $daysInMonth,
|
'month_days' => $daysInMonth,
|
||||||
'user' => $user
|
'user' => $user
|
||||||
]);
|
]);
|
||||||
@ -179,6 +211,19 @@ class UserController extends Controller
|
|||||||
return response()->json($result);
|
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)
|
private function constructActivityGrid(Collection $collection)
|
||||||
{
|
{
|
||||||
$results = [];
|
$results = [];
|
||||||
|
24
app/User.php
24
app/User.php
@ -62,6 +62,23 @@ class User extends Authenticatable
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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' => (!empty($this->profile_alias) ? trim(strtolower($this->profile_alias)) : $this->id)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function groups()
|
public function groups()
|
||||||
{
|
{
|
||||||
return $this->belongsToMany(Group::class, 'user_groups');
|
return $this->belongsToMany(Group::class, 'user_groups');
|
||||||
@ -83,4 +100,11 @@ class User extends Authenticatable
|
|||||||
{
|
{
|
||||||
return trim(!empty($this->profile_alias) ? $this->profile_alias : $this->name);
|
return trim(!empty($this->profile_alias) ? $this->profile_alias : $this->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function unFollowUrl()
|
||||||
|
{
|
||||||
|
return route('unFollowUser', [
|
||||||
|
'idOrAlias' => (!empty($this->profile_alias) ? trim(strtolower($this->profile_alias)) : $this->id)
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
class CreateUserFollowersTable extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('user_followers', function (Blueprint $table) {
|
||||||
|
$table->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');
|
||||||
|
}
|
||||||
|
}
|
@ -41610,6 +41610,101 @@ 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',
|
||||||
|
'<a href="' + data[i].params.user_url + '">' + data[i].params.user_name + '</a>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Photo name
|
||||||
|
if (data[i].params.photo_name && data[i].params.photo_url)
|
||||||
|
{
|
||||||
|
data[i].description = data[i].description
|
||||||
|
.replace(
|
||||||
|
':photo_name',
|
||||||
|
'<a href="' + data[i].params.photo_url + '">' + data[i].params.photo_name + '</a>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
function StorageLocationViewModel()
|
||||||
{
|
{
|
||||||
this.el = '#storage-options';
|
this.el = '#storage-options';
|
||||||
|
2
public/js/blue-twilight.min.js
vendored
2
public/js/blue-twilight.min.js
vendored
File diff suppressed because one or more lines are too long
@ -86,7 +86,8 @@ function UserViewModel(urls)
|
|||||||
this.data = {
|
this.data = {
|
||||||
feed_items: [],
|
feed_items: [],
|
||||||
is_loading: true,
|
is_loading: true,
|
||||||
selected_view: 'profile'
|
selected_view: 'profile',
|
||||||
|
user_id: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
this.computed = {
|
this.computed = {
|
||||||
@ -99,6 +100,16 @@ function UserViewModel(urls)
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.methods = {
|
this.methods = {
|
||||||
|
followUser: function(e)
|
||||||
|
{
|
||||||
|
$.post(urls.follow_user_url, '', function(data)
|
||||||
|
{
|
||||||
|
window.location.reload(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
},
|
||||||
loadFeedItems: function(e)
|
loadFeedItems: function(e)
|
||||||
{
|
{
|
||||||
var self = this;
|
var self = this;
|
||||||
@ -134,14 +145,24 @@ function UserViewModel(urls)
|
|||||||
},
|
},
|
||||||
switchToFeed: function(e) {
|
switchToFeed: function(e) {
|
||||||
this.selected_view = 'feed';
|
this.selected_view = 'feed';
|
||||||
history.pushState('data to be passed', 'Title of the page', urls.current_url + '?tab=feed');
|
history.pushState('', '', urls.current_url + '?tab=feed');
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
switchToProfile: function(e) {
|
switchToProfile: function(e) {
|
||||||
this.selected_view = 'profile';
|
this.selected_view = 'profile';
|
||||||
history.pushState('data to be passed', 'Title of the page', urls.current_url + '?tab=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();
|
e.preventDefault();
|
||||||
return false;
|
return false;
|
||||||
|
@ -96,8 +96,12 @@ return [
|
|||||||
'albums' => 'Albums by :user_name',
|
'albums' => 'Albums by :user_name',
|
||||||
'cameras' => 'Cameras',
|
'cameras' => 'Cameras',
|
||||||
'feed_tab' => 'Activity',
|
'feed_tab' => 'Activity',
|
||||||
|
'follow_button' => 'Follow',
|
||||||
|
'following_button' => 'Following',
|
||||||
'no_albums_p1' => 'No Photo Albums',
|
'no_albums_p1' => 'No Photo Albums',
|
||||||
'no_albums_p2' => ':user_name has not created any albums yet.',
|
'no_albums_p2' => ':user_name has not created any albums yet.',
|
||||||
|
'no_feed_activity_p1' => 'No Activity',
|
||||||
|
'no_feed_activity_p2' => 'There is no activity to show for :user_name.',
|
||||||
'profile_tab' => 'Profile'
|
'profile_tab' => 'Profile'
|
||||||
],
|
],
|
||||||
'user_settings' => [
|
'user_settings' => [
|
||||||
|
@ -14,6 +14,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-8 col-md-9 col-xl-10">
|
<div class="col-sm-8 col-md-9 col-xl-10">
|
||||||
|
@if ($can_follow)
|
||||||
|
@if ($is_following)
|
||||||
|
<a class="pull-right btn btn-lg btn-outline-primary" href="#" v-on:click="unFollowUser"><i class="fa fa-check fa-fw"></i> @lang('gallery.user_profile.following_button')</a>
|
||||||
|
@else
|
||||||
|
<a class="pull-right btn btn-lg btn-primary" href="#" v-on:click="followUser">@lang('gallery.user_profile.follow_button')</a>
|
||||||
|
@endif
|
||||||
|
@endif
|
||||||
<h1>{{ $user->name }}</h1>
|
<h1>{{ $user->name }}</h1>
|
||||||
@if (!empty($user->profile_alias))
|
@if (!empty($user->profile_alias))
|
||||||
<h2 class="text-muted">{{ $user->profile_alias }}</h2>
|
<h2 class="text-muted">{{ $user->profile_alias }}</h2>
|
||||||
@ -124,6 +131,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="text-center" v-if="feed_items.length == 0">
|
||||||
|
<h4 class="text-danger"><b>@lang('gallery.user_profile.no_feed_activity_p1')</b></h4>
|
||||||
|
<p>@lang('gallery.user_profile.no_feed_activity_p2', ['user_name' => $user->name])</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -133,10 +144,13 @@
|
|||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var viewModel = new UserViewModel({
|
var viewModel = new UserViewModel({
|
||||||
current_url: '{{ url()->current() }}',
|
current_url: '{{ url()->current() }}',
|
||||||
feed_url: '{{ \App\User::currentOrAnonymous()->feedJsonUrl() }}'
|
feed_url: '{{ $user->feedJsonUrl() }}',
|
||||||
|
follow_user_url: '{{ $user->followUrl() }}',
|
||||||
|
unfollow_user_url: '{{ $user->unFollowUrl() }}'
|
||||||
});
|
});
|
||||||
|
|
||||||
var app = new Vue(viewModel);
|
var app = new Vue(viewModel);
|
||||||
|
app.user_id = '{{ $user->id }}';
|
||||||
app.loadFeedItems();
|
app.loadFeedItems();
|
||||||
|
|
||||||
@if ($active_tab == 'feed')
|
@if ($active_tab == 'feed')
|
||||||
|
@ -143,6 +143,12 @@ Route::get('label/{labelAlias}', 'Gallery\LabelController@show')
|
|||||||
Route::get('u/{idOrAlias}/feed.json', 'Gallery\UserController@showFeedJson')
|
Route::get('u/{idOrAlias}/feed.json', 'Gallery\UserController@showFeedJson')
|
||||||
->name('viewUserFeedJson')
|
->name('viewUserFeedJson')
|
||||||
->where('idOrAlias', '.*');
|
->where('idOrAlias', '.*');
|
||||||
|
Route::post('u/{idOrAlias}/follow', 'Gallery\UserController@followUser', ['middleware' => 'auth'])
|
||||||
|
->name('followUser')
|
||||||
|
->where('idOrAlias', '.*');
|
||||||
|
Route::post('u/{idOrAlias}/unfollow', 'Gallery\UserController@unFollowUser', ['middleware' => 'auth'])
|
||||||
|
->name('unFollowUser')
|
||||||
|
->where('idOrAlias', '.*');
|
||||||
Route::get('u/{idOrAlias}', 'Gallery\UserController@show')
|
Route::get('u/{idOrAlias}', 'Gallery\UserController@show')
|
||||||
->name('viewUser')
|
->name('viewUser')
|
||||||
->where('idOrAlias', '.*');
|
->where('idOrAlias', '.*');
|
||||||
|
Loading…
Reference in New Issue
Block a user