いいねボタンの追加
This commit is contained in:
parent
34b7cd6c89
commit
225d0854ef
@ -2,7 +2,9 @@
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class Ejaculation extends Model
|
||||
{
|
||||
@ -34,4 +36,25 @@ class Ejaculation extends Model
|
||||
return $v->name;
|
||||
})->all());
|
||||
}
|
||||
|
||||
public function likes()
|
||||
{
|
||||
return $this->hasMany(Like::class);
|
||||
}
|
||||
|
||||
public function scopeWithLikes(Builder $query)
|
||||
{
|
||||
if (Auth::check()) {
|
||||
// (ejaculation_id, user_id) でユニークなわけですが、サブクエリ発行させるのとLeft JoinしてNULLかどうかで結果を見るのどっちがいいんでしょうね
|
||||
return $query->withCount([
|
||||
'likes',
|
||||
'likes as is_liked' => function ($query) {
|
||||
$query->where('user_id', Auth::id());
|
||||
}
|
||||
]);
|
||||
} else {
|
||||
return $query->withCount('likes')
|
||||
->addSelect('0 as is_liked');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -78,7 +78,9 @@ class EjaculationController extends Controller
|
||||
|
||||
public function show($id)
|
||||
{
|
||||
$ejaculation = Ejaculation::findOrFail($id);
|
||||
$ejaculation = Ejaculation::where('id', $id)
|
||||
->withLikes()
|
||||
->firstOrFail();
|
||||
$user = User::findOrFail($ejaculation->user_id);
|
||||
|
||||
// 1つ前のチェックインからの経過時間を求める
|
||||
|
@ -69,6 +69,7 @@ SQL
|
||||
->orderBy('ejaculations.ejaculated_date', 'desc')
|
||||
->select('ejaculations.*')
|
||||
->with('user', 'tags')
|
||||
->withLikes()
|
||||
->take(10)
|
||||
->get();
|
||||
|
||||
|
@ -21,6 +21,7 @@ class SearchController extends Controller
|
||||
->where('is_private', false)
|
||||
->orderBy('ejaculated_date', 'desc')
|
||||
->with(['user', 'tags'])
|
||||
->withLikes()
|
||||
->paginate(20)
|
||||
->appends($inputs);
|
||||
|
||||
|
@ -16,6 +16,7 @@ class TimelineController extends Controller
|
||||
->orderBy('ejaculations.ejaculated_date', 'desc')
|
||||
->select('ejaculations.*')
|
||||
->with('user', 'tags')
|
||||
->withLikes()
|
||||
->paginate(21);
|
||||
|
||||
return view('timeline.public')->with(compact('ejaculations'));
|
||||
|
@ -41,6 +41,7 @@ SQL
|
||||
}
|
||||
$ejaculations = $query->orderBy('ejaculated_date', 'desc')
|
||||
->with('tags')
|
||||
->withLikes()
|
||||
->paginate(20);
|
||||
|
||||
// よく使っているタグ
|
||||
|
@ -51,4 +51,9 @@ class User extends Authenticatable
|
||||
{
|
||||
return Auth::check() && $this->id === Auth::user()->id;
|
||||
}
|
||||
|
||||
public function likes()
|
||||
{
|
||||
return $this->hasMany(Like::class);
|
||||
}
|
||||
}
|
||||
|
65
resources/assets/js/app.js
vendored
65
resources/assets/js/app.js
vendored
@ -27,4 +27,69 @@ $(() => {
|
||||
event.preventDefault();
|
||||
$deleteCheckinModal.modal('show', this);
|
||||
});
|
||||
|
||||
$(document).on('click', '[data-href]', function (event) {
|
||||
location.href = $(this).data('href');
|
||||
});
|
||||
|
||||
$(document).on('click', '.like-button', function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
const $this = $(this);
|
||||
const targetId = $this.data('id');
|
||||
const isLiked = $this.data('liked');
|
||||
|
||||
if (isLiked) {
|
||||
const callback = (data) => {
|
||||
$this.data('liked', false);
|
||||
$this.find('.oi-heart').removeClass('text-danger');
|
||||
|
||||
const count = data.ejaculation ? data.ejaculation.likes_count : 0;
|
||||
$this.find('.like-count').text(count ? count : '');
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: '/api/likes/' + encodeURIComponent(targetId),
|
||||
method: 'delete',
|
||||
type: 'json'
|
||||
})
|
||||
.then(callback)
|
||||
.catch(function (xhr) {
|
||||
if (xhr.status === 404) {
|
||||
callback(JSON.parse(xhr.responseText));
|
||||
return;
|
||||
}
|
||||
|
||||
console.error(xhr);
|
||||
alert('いいねを解除できませんでした。');
|
||||
});
|
||||
} else {
|
||||
const callback = (data) => {
|
||||
$this.data('liked', true);
|
||||
$this.find('.oi-heart').addClass('text-danger');
|
||||
|
||||
const count = data.ejaculation ? data.ejaculation.likes_count : 0;
|
||||
$this.find('.like-count').text(count ? count : '');
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: '/api/likes',
|
||||
method: 'post',
|
||||
type: 'json',
|
||||
data: {
|
||||
id: targetId
|
||||
}
|
||||
})
|
||||
.then(callback)
|
||||
.catch(function (xhr) {
|
||||
if (xhr.status === 409) {
|
||||
callback(JSON.parse(xhr.responseText));
|
||||
return;
|
||||
}
|
||||
|
||||
console.error(xhr);
|
||||
alert('いいねできませんでした。');
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
10
resources/assets/sass/app.scss
vendored
10
resources/assets/sass/app.scss
vendored
@ -12,4 +12,12 @@ $primary: #e53fb1;
|
||||
@import "tissue.css";
|
||||
|
||||
// Components
|
||||
@import "components/link-card";
|
||||
@import "components/link-card";
|
||||
|
||||
.like-button {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.like-count:not(:empty) {
|
||||
padding-left: 0.25rem;
|
||||
}
|
4
resources/assets/sass/tissue.css
vendored
4
resources/assets/sass/tissue.css
vendored
@ -40,10 +40,6 @@
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.timeline-action-item {
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.tis-global-count-graph {
|
||||
height: 90px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, .125);
|
||||
|
35
resources/views/components/ejaculation.blade.php
Normal file
35
resources/views/components/ejaculation.blade.php
Normal file
@ -0,0 +1,35 @@
|
||||
<!-- span -->
|
||||
<div class="d-flex justify-content-between">
|
||||
<h5>
|
||||
<a href="{{ route('user.profile', ['id' => $ejaculation->user->name]) }}" class="text-dark"><img src="{{ $ejaculation->user->getProfileImageUrl(30) }}" width="30" height="30" class="rounded d-inline-block align-bottom"> <bdi>{{ $ejaculation->user->display_name }}</bdi></a>
|
||||
<a href="{{ route('checkin.show', ['id' => $ejaculation->id]) }}" class="text-muted"><small>{{ $ejaculation->ejaculated_date->format('Y/m/d H:i') }}</small></a>
|
||||
</h5>
|
||||
<div>
|
||||
<button type="button" class="btn btn-link text-secondary like-button" data-toggle="tooltip" data-placement="bottom" data-trigger="hover" title="いいね" data-id="{{ $ejaculation->id }}" data-liked="{{ (bool)$ejaculation->is_liked }}"><span class="oi oi-heart {{ $ejaculation->is_liked ? 'text-danger' : '' }}"></span><span class="like-count">{{ $ejaculation->likes_count ? $ejaculation->likes_count : '' }}</span></button>
|
||||
<button type="button" class="btn btn-link text-secondary" data-toggle="tooltip" data-placement="bottom" title="同じオカズでチェックイン" data-href="{{ route('checkin', ['link' => $ejaculation->link, 'tags' => $ejaculation->textTags()]) }}"><span class="oi oi-reload"></span></button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- tags -->
|
||||
@if ($ejaculation->tags->isNotEmpty())
|
||||
<p class="mb-2">
|
||||
@foreach ($ejaculation->tags as $tag)
|
||||
<a class="badge badge-secondary" href="{{ route('search', ['q' => $tag->name]) }}"><span class="oi oi-tag"></span> {{ $tag->name }}</a>
|
||||
@endforeach
|
||||
</p>
|
||||
@endif
|
||||
<!-- okazu link -->
|
||||
@if (!empty($ejaculation->link))
|
||||
<div class="row mx-0">
|
||||
@component('components.link-card', ['link' => $ejaculation->link])
|
||||
@endcomponent
|
||||
<p class="d-flex align-items-baseline mb-2 col-12 px-0">
|
||||
<span class="oi oi-link-intact mr-1"></span><a class="overflow-hidden" href="{{ $ejaculation->link }}" target="_blank" rel="noopener">{{ $ejaculation->link }}</a>
|
||||
</p>
|
||||
</div>
|
||||
@endif
|
||||
<!-- note -->
|
||||
@if (!empty($ejaculation->note))
|
||||
<p class="mb-0 text-break">
|
||||
{!! Formatter::linkify(nl2br(e($ejaculation->note))) !!}
|
||||
</p>
|
||||
@endif
|
@ -33,10 +33,11 @@
|
||||
<div class="d-flex justify-content-between">
|
||||
<h5>{{ $ejaculatedSpan ?? '精通' }} <small class="text-muted">{{ $ejaculation->before_date }}{{ !empty($ejaculation->before_date) ? ' ~ ' : '' }}{{ $ejaculation->ejaculated_date->format('Y/m/d H:i') }}</small></h5>
|
||||
<div>
|
||||
<a class="text-secondary timeline-action-item" href="{{ route('checkin', ['link' => $ejaculation->link, 'tags' => $ejaculation->textTags()]) }}"><span class="oi oi-reload" data-toggle="tooltip" data-placement="bottom" title="同じオカズでチェックイン"></span></a>
|
||||
<button type="button" class="btn btn-link text-secondary like-button" data-toggle="tooltip" data-placement="bottom" data-trigger="hover" title="いいね" data-id="{{ $ejaculation->id }}" data-liked="{{ (bool)$ejaculation->is_liked }}"><span class="oi oi-heart {{ $ejaculation->is_liked ? 'text-danger' : '' }}"></span><span class="like-count">{{ $ejaculation->likes_count ? $ejaculation->likes_count : '' }}</span></button>
|
||||
<button type="button" class="btn btn-link text-secondary" data-toggle="tooltip" data-placement="bottom" title="同じオカズでチェックイン" data-href="{{ route('checkin', ['link' => $ejaculation->link, 'tags' => $ejaculation->textTags()]) }}"><span class="oi oi-reload"></span></button>
|
||||
@if ($user->isMe())
|
||||
<a class="text-secondary timeline-action-item" href="{{ route('checkin.edit', ['id' => $ejaculation->id]) }}"><span class="oi oi-pencil" data-toggle="tooltip" data-placement="bottom" title="修正"></span></a>
|
||||
<a class="text-secondary timeline-action-item" href="#" data-target="#deleteCheckinModal" data-id="{{ $ejaculation->id }}" data-date="{{ $ejaculation->ejaculated_date }}"><span class="oi oi-trash" data-toggle="tooltip" data-placement="bottom" title="削除"></span></a>
|
||||
<button type="button" class="btn btn-link text-secondary" data-toggle="tooltip" data-placement="bottom" title="修正" data-href="{{ route('checkin.edit', ['id' => $ejaculation->id]) }}"><span class="oi oi-pencil"></span></button>
|
||||
<button type="button" class="btn btn-link text-secondary" data-toggle="tooltip" data-placement="bottom" title="削除" data-target="#deleteCheckinModal" data-id="{{ $ejaculation->id }}" data-date="{{ $ejaculation->ejaculated_date }}"><span class="oi oi-trash"></span></button>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
@ -55,40 +55,8 @@
|
||||
<ul class="list-group">
|
||||
@foreach ($publicLinkedEjaculations as $ejaculation)
|
||||
<li class="list-group-item no-side-border pt-3 pb-3 text-break">
|
||||
<!-- span -->
|
||||
<div class="d-flex justify-content-between">
|
||||
<h5>
|
||||
<a href="{{ route('user.profile', ['id' => $ejaculation->user->name]) }}" class="text-dark"><img src="{{ $ejaculation->user->getProfileImageUrl(30) }}" width="30" height="30" class="rounded d-inline-block align-bottom"> <bdi>{{ $ejaculation->user->display_name }}</bdi></a>
|
||||
<a href="{{ route('checkin.show', ['id' => $ejaculation->id]) }}" class="text-muted"><small>{{ $ejaculation->ejaculated_date->format('Y/m/d H:i') }}</small></a>
|
||||
</h5>
|
||||
<div>
|
||||
<a class="text-secondary timeline-action-item" href="{{ route('checkin', ['link' => $ejaculation->link, 'tags' => $ejaculation->textTags()]) }}"><span class="oi oi-reload" data-toggle="tooltip" data-placement="bottom" title="同じオカズでチェックイン"></span></a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- tags -->
|
||||
@if ($ejaculation->tags->isNotEmpty())
|
||||
<p class="mb-2">
|
||||
@foreach ($ejaculation->tags as $tag)
|
||||
<a class="badge badge-secondary" href="{{ route('search', ['q' => $tag->name]) }}"><span class="oi oi-tag"></span> {{ $tag->name }}</a>
|
||||
@endforeach
|
||||
</p>
|
||||
@endif
|
||||
<!-- okazu link -->
|
||||
@if (!empty($ejaculation->link))
|
||||
<div class="row mx-0">
|
||||
@component('components.link-card', ['link' => $ejaculation->link])
|
||||
@endcomponent
|
||||
<p class="d-flex align-items-baseline mb-2 col-12 px-0">
|
||||
<span class="oi oi-link-intact mr-1"></span><a class="overflow-hidden" href="{{ $ejaculation->link }}" target="_blank" rel="noopener">{{ $ejaculation->link }}</a>
|
||||
</p>
|
||||
</div>
|
||||
@endif
|
||||
<!-- note -->
|
||||
@if (!empty($ejaculation->note))
|
||||
<p class="mb-0 text-break">
|
||||
{!! Formatter::linkify(nl2br(e($ejaculation->note))) !!}
|
||||
</p>
|
||||
@endif
|
||||
@component('components.ejaculation', compact('ejaculation'))
|
||||
@endcomponent
|
||||
</li>
|
||||
@endforeach
|
||||
<li class="list-group-item no-side-border text-right">
|
||||
|
@ -7,43 +7,8 @@
|
||||
<ul class="list-group">
|
||||
@foreach($results as $ejaculation)
|
||||
<li class="list-group-item border-bottom-only pt-3 pb-3 text-break">
|
||||
<!-- span -->
|
||||
<div class="d-flex justify-content-between">
|
||||
<h5>
|
||||
<a href="{{ route('user.profile', ['id' => $ejaculation->user->name]) }}" class="text-dark"><img src="{{ $ejaculation->user->getProfileImageUrl(30) }}" width="30" height="30" class="rounded d-inline-block align-bottom"> <bdi>{{ $ejaculation->user->display_name }}</bdi></a>
|
||||
<a href="{{ route('checkin.show', ['id' => $ejaculation->id]) }}" class="text-muted"><small>{{ $ejaculation->ejaculated_date->format('Y/m/d H:i') }}</small></a>
|
||||
</h5>
|
||||
<div>
|
||||
<a class="text-secondary timeline-action-item" href="{{ route('checkin', ['link' => $ejaculation->link, 'tags' => $ejaculation->textTags()]) }}"><span class="oi oi-reload" data-toggle="tooltip" data-placement="bottom" title="同じオカズでチェックイン"></span></a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- tags -->
|
||||
@if ($ejaculation->is_private || $ejaculation->tags->isNotEmpty())
|
||||
<p class="mb-2">
|
||||
@if ($ejaculation->is_private)
|
||||
<span class="badge badge-warning"><span class="oi oi-lock-locked"></span> 非公開</span>
|
||||
@endif
|
||||
@foreach ($ejaculation->tags as $tag)
|
||||
<a class="badge badge-secondary" href="{{ route('search', ['q' => $tag->name]) }}"><span class="oi oi-tag"></span> {{ $tag->name }}</a>
|
||||
@endforeach
|
||||
</p>
|
||||
@endif
|
||||
<!-- okazu link -->
|
||||
@if (!empty($ejaculation->link))
|
||||
<div class="row mx-0">
|
||||
@component('components.link-card', ['link' => $ejaculation->link])
|
||||
@endcomponent
|
||||
<p class="d-flex align-items-baseline mb-2 col-12 px-0">
|
||||
<span class="oi oi-link-intact mr-1"></span><a class="overflow-hidden" href="{{ $ejaculation->link }}" target="_blank" rel="noopener">{{ $ejaculation->link }}</a>
|
||||
</p>
|
||||
</div>
|
||||
@endif
|
||||
<!-- note -->
|
||||
@if (!empty($ejaculation->note))
|
||||
<p class="mb-0 text-break">
|
||||
{!! Formatter::linkify(nl2br(e($ejaculation->note))) !!}
|
||||
</p>
|
||||
@endif
|
||||
@component('components.ejaculation', compact('ejaculation'))
|
||||
@endcomponent
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
|
@ -11,43 +11,8 @@
|
||||
<div class="row mx-1">
|
||||
@foreach($ejaculations as $ejaculation)
|
||||
<div class="col-12 col-lg-6 col-xl-4 py-3 text-break border-top">
|
||||
<!-- span -->
|
||||
<div class="d-flex justify-content-between">
|
||||
<h5>
|
||||
<a href="{{ route('user.profile', ['id' => $ejaculation->user->name]) }}" class="text-dark"><img src="{{ $ejaculation->user->getProfileImageUrl(30) }}" width="30" height="30" class="rounded d-inline-block align-bottom"> <bdi>{{ $ejaculation->user->display_name }}</bdi></a>
|
||||
<a href="{{ route('checkin.show', ['id' => $ejaculation->id]) }}" class="text-muted"><small>{{ $ejaculation->ejaculated_date->format('Y/m/d H:i') }}</small></a>
|
||||
</h5>
|
||||
<div>
|
||||
<a class="text-secondary timeline-action-item" href="{{ route('checkin', ['link' => $ejaculation->link, 'tags' => $ejaculation->textTags()]) }}"><span class="oi oi-reload" data-toggle="tooltip" data-placement="bottom" title="同じオカズでチェックイン"></span></a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- tags -->
|
||||
@if ($ejaculation->is_private || $ejaculation->tags->isNotEmpty())
|
||||
<p class="mb-2">
|
||||
@if ($ejaculation->is_private)
|
||||
<span class="badge badge-warning"><span class="oi oi-lock-locked"></span> 非公開</span>
|
||||
@endif
|
||||
@foreach ($ejaculation->tags as $tag)
|
||||
<a class="badge badge-secondary" href="{{ route('search', ['q' => $tag->name]) }}"><span class="oi oi-tag"></span> {{ $tag->name }}</a>
|
||||
@endforeach
|
||||
</p>
|
||||
@endif
|
||||
<!-- okazu link -->
|
||||
@if (!empty($ejaculation->link))
|
||||
<div class="row mx-0">
|
||||
@component('components.link-card', ['link' => $ejaculation->link])
|
||||
@endcomponent
|
||||
<p class="d-flex align-items-baseline mb-2 col-12 px-0">
|
||||
<span class="oi oi-link-intact mr-1"></span><a class="overflow-hidden" href="{{ $ejaculation->link }}" target="_blank" rel="noopener">{{ $ejaculation->link }}</a>
|
||||
</p>
|
||||
</div>
|
||||
@endif
|
||||
<!-- note -->
|
||||
@if (!empty($ejaculation->note))
|
||||
<p class="mb-0 text-break">
|
||||
{!! Formatter::linkify(nl2br(e($ejaculation->note))) !!}
|
||||
</p>
|
||||
@endif
|
||||
@component('components.ejaculation', compact('ejaculation'))
|
||||
@endcomponent
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
@ -39,10 +39,11 @@
|
||||
<div class="d-flex justify-content-between">
|
||||
<h5>{{ $ejaculation->ejaculated_span ?? '精通' }} <a href="{{ route('checkin.show', ['id' => $ejaculation->id]) }}" class="text-muted"><small>{{ $ejaculation->before_date }}{{ !empty($ejaculation->before_date) ? ' ~ ' : '' }}{{ $ejaculation->ejaculated_date->format('Y/m/d H:i') }}</small></a></h5>
|
||||
<div>
|
||||
<a class="text-secondary timeline-action-item" href="{{ route('checkin', ['link' => $ejaculation->link, 'tags' => $ejaculation->textTags()]) }}"><span class="oi oi-reload" data-toggle="tooltip" data-placement="bottom" title="同じオカズでチェックイン"></span></a>
|
||||
<button type="button" class="btn btn-link text-secondary like-button" data-toggle="tooltip" data-placement="bottom" data-trigger="hover" title="いいね" data-id="{{ $ejaculation->id }}" data-liked="{{ (bool)$ejaculation->is_liked }}"><span class="oi oi-heart {{ $ejaculation->is_liked ? 'text-danger' : '' }}"></span><span class="like-count">{{ $ejaculation->likes_count ? $ejaculation->likes_count : '' }}</span></button>
|
||||
<button type="button" class="btn btn-link text-secondary" data-toggle="tooltip" data-placement="bottom" title="同じオカズでチェックイン" data-href="{{ route('checkin', ['link' => $ejaculation->link, 'tags' => $ejaculation->textTags()]) }}"><span class="oi oi-reload"></span></button>
|
||||
@if ($user->isMe())
|
||||
<a class="text-secondary timeline-action-item" href="{{ route('checkin.edit', ['id' => $ejaculation->id]) }}"><span class="oi oi-pencil" data-toggle="tooltip" data-placement="bottom" title="修正"></span></a>
|
||||
<a class="text-secondary timeline-action-item" href="#" data-target="#deleteCheckinModal" data-id="{{ $ejaculation->id }}" data-date="{{ $ejaculation->ejaculated_date }}"><span class="oi oi-trash" data-toggle="tooltip" data-placement="bottom" title="削除"></span></a>
|
||||
<button type="button" class="btn btn-link text-secondary" data-toggle="tooltip" data-placement="bottom" title="修正" data-href="{{ route('checkin.edit', ['id' => $ejaculation->id]) }}"><span class="oi oi-pencil"></span></button>
|
||||
<button type="button" class="btn btn-link text-secondary" data-toggle="tooltip" data-placement="bottom" title="削除" data-target="#deleteCheckinModal" data-id="{{ $ejaculation->id }}" data-date="{{ $ejaculation->ejaculated_date }}"><span class="oi oi-trash"></span></button>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user