Merge pull request #173 from shikorism/koresuki

チェックインに対する「いいね」機能
This commit is contained in:
shibafu
2019-04-17 19:55:42 +09:00
committed by GitHub
31 changed files with 535 additions and 136 deletions

View File

@@ -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('いいねできませんでした。');
});
}
});
});

View File

@@ -12,4 +12,5 @@ $primary: #e53fb1;
@import "tissue.css";
// Components
@import "components/link-card";
@import "components/ejaculation";
@import "components/link-card";

View File

@@ -0,0 +1,21 @@
.ejaculation-actions {
& > button:not(:last-child) {
margin-right: 24px;
}
}
.like-button {
text-decoration: none !important;
}
.like-count:not(:empty) {
padding-left: 0.5rem;
}
.like-users {
height: 30px;
}
.like-users-tall {
height: 36px;
}

View File

@@ -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);

View File

@@ -0,0 +1,53 @@
<!-- span -->
<div>
<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>
<!-- 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-2 text-break">
{!! Formatter::linkify(nl2br(e($ejaculation->note))) !!}
</p>
@endif
<!-- likes -->
@if ($ejaculation->likes_count > 0)
<div class="my-2 py-1 border-top border-bottom d-flex align-items-center">
<div class="ml-2 mr-3 text-secondary flex-shrink-0"><small><strong>{{ $ejaculation->likes_count }}</strong> 件のいいね</small></div>
<div class="like-users flex-grow-1 overflow-hidden">
@foreach ($ejaculation->likes as $like)
@if ($like->user !== null)
<a href="{{ route('user.profile', ['name' => $like->user->name]) }}"><img src="{{ $like->user->getProfileImageUrl(30) }}" width="30" height="30" class="rounded" data-toggle="tooltip" data-placement="bottom" title="{{ $like->user->display_name }}"></a>
@endif
@endforeach
</div>
</div>
@endif
<!-- actions -->
<div class="ejaculation-actions">
<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>
<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>
</div>

View File

@@ -30,15 +30,8 @@
<div class="card">
<div class="card-body">
<!-- span -->
<div class="d-flex justify-content-between">
<div>
<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>
@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>
@endif
</div>
</div>
<!-- tags -->
@if ($ejaculation->is_private || $ejaculation->tags->isNotEmpty())
@@ -63,10 +56,32 @@
@endif
<!-- note -->
@if (!empty($ejaculation->note))
<p class="mb-0 text-break">
<p class="mb-2 text-break">
{!! Formatter::linkify(nl2br(e($ejaculation->note))) !!}
</p>
@endif
<!-- likes -->
@if ($ejaculation->likes_count > 0)
<div class="my-2 py-1 border-top border-bottom d-flex align-items-center">
<div class="ml-2 mr-3 text-secondary flex-shrink-0"><small><strong>{{ $ejaculation->likes_count }}</strong> 件のいいね</small></div>
<div class="like-users-tall flex-grow-1 overflow-hidden">
@foreach ($ejaculation->likes as $like)
@if ($like->user !== null)
<a href="{{ route('user.profile', ['name' => $like->user->name]) }}"><img src="{{ $like->user->getProfileImageUrl(36) }}" width="36" height="36" class="rounded" data-toggle="tooltip" data-placement="bottom" title="{{ $like->user->display_name }}"></a>
@endif
@endforeach
</div>
</div>
@endif
<!-- actions -->
<div class="ejaculation-actions">
<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>
<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>
@if ($user->isMe())
<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>
</div>
@endif

View File

@@ -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">

View File

@@ -50,6 +50,9 @@
</p>
</a>
<div class="dropdown-divider"></div>
<a href="{{ route('user.profile', ['name' => Auth::user()->name]) }}" class="dropdown-item">プロフィール</a>
<a href="{{ route('user.likes', ['name' => Auth::user()->name]) }}" class="dropdown-item">いいね</a>
<div class="dropdown-divider"></div>
<a href="{{ route('setting') }}" class="dropdown-item">設定</a>
<a href="{{ route('logout') }}" class="dropdown-item" onclick="event.preventDefault(); document.getElementById('logout-form').submit();">ログアウト</a>
</div>
@@ -104,6 +107,9 @@
</p>
</a>
<div class="dropdown-divider"></div>
<a href="{{ route('user.profile', ['name' => Auth::user()->name]) }}" class="dropdown-item">プロフィール</a>
<a href="{{ route('user.likes', ['name' => Auth::user()->name]) }}" class="dropdown-item">いいね</a>
<div class="dropdown-divider"></div>
<a href="{{ route('setting') }}" class="dropdown-item">設定</a>
@can ('admin')
<a href="{{ route('admin.dashboard') }}" class="dropdown-item">管理</a>

View File

@@ -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>

View File

@@ -11,6 +11,11 @@
<div class="custom-control custom-checkbox mb-2">
<input id="protected" name="is_protected" class="custom-control-input" type="checkbox" {{ (old('is_protected') ?? Auth::user()->is_protected ) ? 'checked' : '' }}>
<label class="custom-control-label" for="protected">全てのチェックイン履歴を非公開にする</label>
<small class="form-text text-muted">プロフィール情報を除いて、全ての情報が非公開になります。</small>
</div>
<div class="custom-control custom-checkbox mb-2">
<input id="private-likes" name="private_likes" class="custom-control-input" type="checkbox" {{ (old('private_likes') ?? Auth::user()->private_likes ) ? 'checked' : '' }}>
<label class="custom-control-label" for="private-likes">いいねしたチェックイン一覧を非公開にする</label>
</div>
<div class="custom-control custom-checkbox">
<input id="accept-analytics" name="accept_analytics" class="custom-control-input" type="checkbox" {{ (old('accept_analytics') ?? Auth::user()->accept_analytics ) ? 'checked' : '' }}>

View File

@@ -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>

View File

@@ -20,6 +20,13 @@
<li class="nav-item">
<a class="nav-link {{ Route::currentRouteName() === 'user.okazu' ? 'active' : '' }}" href="{{ route('user.okazu', ['name' => $user->name]) }}">オカズ</a>
</li>
<li class="nav-item">
<a class="nav-link {{ Route::currentRouteName() === 'user.likes' ? 'active' : '' }}" href="{{ route('user.likes', ['name' => $user->name]) }}">いいね
@if ($user->isMe() || !($user->is_protected || $user->private_likes))
<span class="badge badge-primary">{{ $user->likes()->count() }}</span>
@endif
</a>
</li>
</ul>
<div class="tab-content">
@yield('tab-content')

View File

@@ -0,0 +1,25 @@
@extends('user.base')
@section('title', $user->display_name . ' (@' . $user->name . ') さんがいいねしたチェックイン')
@section('tab-content')
@if (($user->is_protected || $user->private_likes) && !$user->isMe())
<p class="mt-4">
<span class="oi oi-lock-locked"></span> このユーザはいいね一覧を公開していません。
</p>
@else
<ul class="list-group">
@forelse ($likes as $like)
<li class="list-group-item border-bottom-only pt-3 pb-3 text-break">
@component('components.ejaculation', ['ejaculation' => $like->ejaculation])
@endcomponent
</li>
@empty
<li class="list-group-item border-bottom-only">
<p>まだ何もいいと思ったことがありません。</p>
</li>
@endforelse
</ul>
{{ $likes->links(null, ['className' => 'mt-4 justify-content-center']) }}
@endif
@endsection

View File

@@ -36,15 +36,8 @@
@forelse ($ejaculations as $ejaculation)
<li class="list-group-item border-bottom-only pt-3 pb-3 text-break">
<!-- span -->
<div class="d-flex justify-content-between">
<div>
<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>
@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>
@endif
</div>
</div>
<!-- tags -->
@if ($ejaculation->is_private || $ejaculation->tags->isNotEmpty())
@@ -69,10 +62,32 @@
@endif
<!-- note -->
@if (!empty($ejaculation->note))
<p class="mb-0 text-break">
<p class="mb-2 text-break">
{!! Formatter::linkify(nl2br(e($ejaculation->note))) !!}
</p>
@endif
<!-- likes -->
@if ($ejaculation->likes_count > 0)
<div class="my-2 py-1 border-top border-bottom d-flex align-items-center">
<div class="ml-2 mr-3 text-secondary flex-shrink-0"><small><strong>{{ $ejaculation->likes_count }}</strong> 件のいいね</small></div>
<div class="like-users flex-grow-1 overflow-hidden">
@foreach ($ejaculation->likes as $like)
@if ($like->user !== null)
<a href="{{ route('user.profile', ['name' => $like->user->name]) }}"><img src="{{ $like->user->getProfileImageUrl(30) }}" width="30" height="30" class="rounded" data-toggle="tooltip" data-placement="bottom" title="{{ $like->user->display_name }}"></a>
@endif
@endforeach
</div>
</div>
@endif
<!-- actions -->
<div class="ejaculation-actions">
<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>
<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>
@if ($user->isMe())
<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>
</li>
@empty
<li class="list-group-item border-bottom-only">