diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index a5b257d..9fc045b 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -36,6 +36,31 @@ class HomeController extends Controller $categories = Information::CATEGORIES; if (Auth::check()) { + // チェックイン動向グラフ用のデータ取得 + $groupByDay = Ejaculation::select(DB::raw( + <<<'SQL' +to_char(ejaculated_date, 'YYYY/MM/DD') AS "date", +count(*) AS "count" +SQL + )) + ->join('users', function ($join) { + $join->on('users.id', '=', 'ejaculations.user_id') + ->where('users.accept_analytics', true); + }) + ->where('ejaculated_date', '>=', now()->subDays(14)) + ->groupBy(DB::raw("to_char(ejaculated_date, 'YYYY/MM/DD')")) + ->orderBy(DB::raw("to_char(ejaculated_date, 'YYYY/MM/DD')")) + ->get() + ->mapWithKeys(function ($item) { + return [$item['date'] => $item['count']]; + }); + $globalEjaculationCounts = []; + $day = Carbon::now()->subDays(29); + for ($i = 0; $i < 30; $i++) { + $globalEjaculationCounts[$day->format('Y/m/d') . ' の総チェックイン数'] = $groupByDay[$day->format('Y/m/d')] ?? 0; + $day->addDay(); + } + // お惣菜コーナー用のデータ取得 $publicLinkedEjaculations = Ejaculation::join('users', 'users.id', '=', 'ejaculations.user_id') ->where('users.is_protected', false) @@ -47,7 +72,7 @@ class HomeController extends Controller ->take(10) ->get(); - return view('home')->with(compact('informations', 'categories', 'publicLinkedEjaculations')); + return view('home')->with(compact('informations', 'categories', 'globalEjaculationCounts', 'publicLinkedEjaculations')); } else { return view('guest')->with(compact('informations', 'categories')); } diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index f0eb1d4..3fa2725 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -21,7 +21,8 @@ class SearchController extends Controller ->where('is_private', false) ->orderBy('ejaculated_date', 'desc') ->with(['user', 'tags']) - ->paginate(20); + ->paginate(20) + ->appends($inputs); return view('search.index')->with(compact('inputs', 'results')); } @@ -34,7 +35,8 @@ class SearchController extends Controller $results = Tag::query() ->where('name', 'like', "%{$inputs['q']}%") - ->paginate(50); + ->paginate(50) + ->appends($inputs); return view('search.relatedTag')->with(compact('inputs', 'results')); } diff --git a/app/Http/Controllers/SettingController.php b/app/Http/Controllers/SettingController.php index 1f812e3..136f055 100644 --- a/app/Http/Controllers/SettingController.php +++ b/app/Http/Controllers/SettingController.php @@ -17,9 +17,13 @@ class SettingController extends Controller { $inputs = $request->all(); $validator = Validator::make($inputs, [ - 'display_name' => 'required|string|max:20' + 'display_name' => 'required|string|max:20', + 'bio' => 'nullable|string|max:160', + 'url' => 'nullable|url|max:2000' ], [], [ - 'display_name' => '名前' + 'display_name' => '名前', + 'bio' => '自己紹介', + 'url' => 'URL' ]); if ($validator->fails()) { @@ -28,6 +32,8 @@ class SettingController extends Controller $user = Auth::user(); $user->display_name = $inputs['display_name']; + $user->bio = $inputs['bio'] ?? ''; + $user->url = $inputs['url'] ?? ''; $user->save(); return redirect()->route('setting')->with('status', 'プロフィールを更新しました。'); diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index beb463c..8f6f909 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -102,7 +102,7 @@ SQL } // 月間グラフ用の配列初期化 - $month = Carbon::now()->subMonth(11)->firstOfMonth(); // 直近12ヶ月 + $month = Carbon::now()->firstOfMonth()->subMonth(11); // 直近12ヶ月 for ($i = 0; $i < 12; $i++) { $monthlySum[$month->format('Y/m')] = 0; $month->addMonth(); diff --git a/app/Http/Middleware/RedirectIfAuthenticated.php b/app/Http/Middleware/RedirectIfAuthenticated.php index e4cec9c..b256b2d 100644 --- a/app/Http/Middleware/RedirectIfAuthenticated.php +++ b/app/Http/Middleware/RedirectIfAuthenticated.php @@ -18,7 +18,7 @@ class RedirectIfAuthenticated public function handle($request, Closure $next, $guard = null) { if (Auth::guard($guard)->check()) { - return redirect('/home'); + return redirect()->route('home'); } return $next($request); diff --git a/app/Http/ViewComposers/ProfileComposer.php b/app/Http/ViewComposers/ProfileStatsComposer.php similarity index 98% rename from app/Http/ViewComposers/ProfileComposer.php rename to app/Http/ViewComposers/ProfileStatsComposer.php index cbb9a85..6f93ded 100644 --- a/app/Http/ViewComposers/ProfileComposer.php +++ b/app/Http/ViewComposers/ProfileStatsComposer.php @@ -7,7 +7,7 @@ use Carbon\Carbon; use Illuminate\Support\Facades\DB; use Illuminate\View\View; -class ProfileComposer +class ProfileStatsComposer { public function __construct() { diff --git a/app/MetadataResolver/ActivityPubResolver.php b/app/MetadataResolver/ActivityPubResolver.php new file mode 100644 index 0000000..981cf9d --- /dev/null +++ b/app/MetadataResolver/ActivityPubResolver.php @@ -0,0 +1,80 @@ +activityClient = new \GuzzleHttp\Client([ + 'headers' => [ + 'Accept' => 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"' + ] + ]); + } + + public function resolve(string $url): Metadata + { + $res = $this->activityClient->get($url); + if ($res->getStatusCode() === 200) { + return $this->parse($res->getBody()); + } else { + throw new \RuntimeException("{$res->getStatusCode()}: $url"); + } + } + + public function parse(string $json): Metadata + { + $activityOrObject = json_decode($json, true); + $object = $activityOrObject['object'] ?? $activityOrObject; + + $metadata = new Metadata(); + + $metadata->title = isset($object['attributedTo']) ? $this->getTitleFromActor($object['attributedTo']) : ''; + $metadata->description .= isset($object['summary']) ? $object['summary'] . " | " : ''; + $metadata->description .= isset($object['content']) ? $this->html2text($object['content']) : ''; + $metadata->image = $object['attachment'][0]['url'] ?? ''; + + return $metadata; + } + + private function getTitleFromActor(string $url): string + { + try { + $res = $this->activityClient->get($url); + if ($res->getStatusCode() !== 200) { + Log::info(self::class . ': Actorの取得に失敗 URL=' . $url); + return ''; + } + + $actor = json_decode($res->getBody(), true); + $title = $actor['name'] ?? ''; + if (isset($actor['preferredUsername'])) { + $title .= ' (@' . $actor['preferredUsername'] . '@' . parse_url($actor['id'], PHP_URL_HOST) . ')'; + } + + return $title; + } catch (TransferException $e) { + Log::info(self::class . ': Actorの取得に失敗 URL=' . $url); + return ''; + } + } + + private function html2text(string $html): string + { + $html = mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'); + $html = preg_replace('~|

\s*]*>~i', "\n", $html); + $dom = new \DOMDocument(); + $dom->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); + return $dom->textContent; + } +} diff --git a/app/MetadataResolver/MetadataResolver.php b/app/MetadataResolver/MetadataResolver.php index 246a77a..a00625b 100644 --- a/app/MetadataResolver/MetadataResolver.php +++ b/app/MetadataResolver/MetadataResolver.php @@ -2,6 +2,9 @@ namespace App\MetadataResolver; +use GuzzleHttp\Exception\ClientException; +use GuzzleHttp\Exception\ServerException; + class MetadataResolver implements Resolver { public $rules = [ @@ -15,10 +18,20 @@ class MetadataResolver implements Resolver '~www\.pixiv\.net/member_illust\.php\?illust_id=\d+~' => PixivResolver::class, '~fantia\.jp/posts/\d+~' => FantiaResolver::class, '~dmm\.co\.jp/~' => FanzaResolver::class, + '~www\.patreon\.com/~' => PatreonResolver::class, '~www\.deviantart\.com/.*/art/.*~' => DeviantArtResolver::class, - '/.*/' => OGPResolver::class + '~\.syosetu\.com/n\d+[a-z]{2,}~' => NarouResolver::class, ]; + public $mimeTypes = [ + 'application/activity+json' => ActivityPubResolver::class, + 'application/ld+json' => ActivityPubResolver::class, + 'text/html' => OGPResolver::class, + '*/*' => OGPResolver::class + ]; + + public $defaultResolver = OGPResolver::class; + public function resolve(string $url): Metadata { foreach ($this->rules as $pattern => $class) { @@ -29,6 +42,64 @@ class MetadataResolver implements Resolver } } + $result = $this->resolveWithAcceptHeader($url); + if ($result !== null) { + return $result; + } + + if (isset($this->defaultResolver)) { + $resolver = new $this->defaultResolver(); + return $resolver->resolve($url); + } + throw new \UnexpectedValueException('URL not matched.'); } + + public function resolveWithAcceptHeader(string $url): ?Metadata + { + try { + // Rails等はAcceptに */* が入っていると、ブラウザの適当なAcceptヘッダだと判断して全部無視してしまう。 + // c.f. https://github.com/rails/rails/issues/9940 + // そこでここでは */* を「Acceptヘッダを無視してきたレスポンス(よくある)」のハンドラとして扱い、 + // Acceptヘッダには */* を足さないことにする。 + $acceptTypes = array_diff(array_keys($this->mimeTypes), ['*/*']); + + $client = new \GuzzleHttp\Client(); + $res = $client->request('GET', $url, [ + 'headers' => [ + 'Accept' => implode(', ', $acceptTypes) + ] + ]); + + if ($res->getStatusCode() === 200) { + preg_match('/^[^;\s]+/', $res->getHeaderLine('Content-Type'), $matches); + $mimeType = $matches[0]; + + if (isset($this->mimeTypes[$mimeType])) { + $class = $this->mimeTypes[$mimeType]; + $parser = new $class(); + + return $parser->parse($res->getBody()); + } + + if (isset($this->mimeTypes['*/*'])) { + $class = $this->mimeTypes['*/*']; + $parser = new $class(); + + return $parser->parse($res->getBody()); + } + } else { + // code < 400 && code !== 200 => fallback + } + } catch (ClientException $e) { + // 406 Not Acceptable は多分Acceptが原因なので無視してフォールバック + if ($e->getResponse()->getStatusCode() !== 406) { + throw $e; + } + } catch (ServerException $e) { + // 5xx は変なAcceptが原因かもしれない(?)ので無視してフォールバック + } + + return null; + } } diff --git a/app/MetadataResolver/NarouResolver.php b/app/MetadataResolver/NarouResolver.php new file mode 100644 index 0000000..5f14d0a --- /dev/null +++ b/app/MetadataResolver/NarouResolver.php @@ -0,0 +1,46 @@ + 'yes'], '.syosetu.com'); + + $client = new \GuzzleHttp\Client(); + $res = $client->get($url, ['cookies' => $cookieJar]); + if ($res->getStatusCode() === 200) { + $ogpResolver = new OGPResolver(); + $metadata = $ogpResolver->parse($res->getBody()); + $metadata->description = ''; + + $dom = new \DOMDocument(); + @$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'ASCII,JIS,UTF-8,eucJP-win,SJIS-win')); + $xpath = new \DOMXPath($dom); + + $description = []; + + // 作者名 + $writerNodes = $xpath->query('//*[contains(@class, "novel_writername")]'); + if ($writerNodes->length !== 0 && !empty($writerNodes->item(0)->textContent)) { + $description[] = trim($writerNodes->item(0)->textContent); + } + + // あらすじ + $exNodes = $xpath->query('//*[@id="novel_ex"]'); + if ($exNodes->length !== 0 && !empty($exNodes->item(0)->textContent)) { + $summary = trim($exNodes->item(0)->textContent); + $description[] = mb_strimwidth($summary, 0, 101, '…'); // 100 + '…'(1) + } + + $metadata->description = implode(' / ', $description); + + return $metadata; + } else { + throw new \RuntimeException("{$res->getStatusCode()}: $url"); + } + } +} diff --git a/app/MetadataResolver/OGPResolver.php b/app/MetadataResolver/OGPResolver.php index 5afe83c..1cf4c1e 100644 --- a/app/MetadataResolver/OGPResolver.php +++ b/app/MetadataResolver/OGPResolver.php @@ -2,7 +2,7 @@ namespace App\MetadataResolver; -class OGPResolver implements Resolver +class OGPResolver implements Resolver, Parser { public function resolve(string $url): Metadata { @@ -30,7 +30,7 @@ class OGPResolver implements Resolver $metadata->title = $nodes->item(0)->textContent; } } - $metadata->description = $this->findContent($xpath, '//meta[@*="og:description"]', '//meta[@*="twitter:description"]'); + $metadata->description = $this->findContent($xpath, '//meta[@*="og:description"]', '//meta[@*="twitter:description"]', '//meta[@name="description"]'); $metadata->image = $this->findContent($xpath, '//meta[@*="og:image"]', '//meta[@*="twitter:image"]'); return $metadata; diff --git a/app/MetadataResolver/Parser.php b/app/MetadataResolver/Parser.php new file mode 100644 index 0000000..f9effde --- /dev/null +++ b/app/MetadataResolver/Parser.php @@ -0,0 +1,8 @@ +get($url); + if ($res->getStatusCode() === 200) { + $ogpResolver = new OGPResolver(); + $metadata = $ogpResolver->parse($res->getBody()); + + parse_str(parse_url($metadata->image, PHP_URL_QUERY), $temp); + $expires_at_unixtime = $temp["token-time"]; + $expires_at = Carbon::createFromTimestamp($expires_at_unixtime); + + $metadata->expires_at = $expires_at; + + return $metadata; + } else { + throw new \RuntimeException("{$res->getStatusCode()}: $url"); + } + } +} diff --git a/app/MetadataResolver/PixivResolver.php b/app/MetadataResolver/PixivResolver.php index ca5463c..16c89d9 100644 --- a/app/MetadataResolver/PixivResolver.php +++ b/app/MetadataResolver/PixivResolver.php @@ -8,6 +8,7 @@ class PixivResolver implements Resolver * サムネイル画像 URL から最大長辺 1200px の画像 URL に変換する * * @param string $thumbnailUrl サムネイル画像 URL + * * @return string 1200px の画像 URL */ public function thumbnailToMasterUrl(string $thumbnailUrl): string @@ -23,6 +24,7 @@ class PixivResolver implements Resolver * HUGE THANKS TO PIXIV.CAT! * * @param string $pixivUrl i.pximg URL + * * @return string i.pixiv.cat URL */ public function proxize(string $pixivUrl): string @@ -32,62 +34,33 @@ class PixivResolver implements Resolver public function resolve(string $url): Metadata { - preg_match("~illust_id=(\d+)~", parse_url($url)['query'], $match); - $illustId = $match[1]; + parse_str(parse_url($url, PHP_URL_QUERY), $params); + $illustId = $params['illust_id']; - // 漫画ページかつページ数あり - if (strpos(parse_url($url)['query'], 'mode=manga_big') && strpos(parse_url($url)['query'], 'page=')) { - preg_match("~page=(\d+)~", parse_url($url)['query'], $match); - $page = $match[1]; + // 漫画ページ(ページ数はmanga_bigならあるかも) + if ($params['mode'] === 'manga_big' || $params['mode'] === 'manga') { + $page = $params['page'] ?? 0; // 未ログインでは漫画ページを開けないため、URL を作品ページに変換する - $url = str_replace('mode=manga_big', 'mode=medium', $url); + $url = preg_replace('~mode=manga(_big)?~', 'mode=medium', $url); + } - $client = new \GuzzleHttp\Client(); - $res = $client->get($url); - if ($res->getStatusCode() === 200) { - $ogpResolver = new OGPResolver(); - $metadata = $ogpResolver->parse($res->getBody()); + $client = new \GuzzleHttp\Client(); + $res = $client->get($url); + if ($res->getStatusCode() === 200) { + $ogpResolver = new OGPResolver(); + $metadata = $ogpResolver->parse($res->getBody()); - preg_match("~https://i\.pximg\.net/c/128x128/img-master/img/\d{4}/\d{2}/\d{2}/\d{2}/\d{2}/\d{2}/{$illustId}_p0_square1200\.jpg~", $res->getBody(), $match); - $illustThumbnailUrl = $match[0]; + preg_match("~https://i\.pximg\.net/c/128x128/img-master/img/\d{4}/\d{2}/\d{2}/\d{2}/\d{2}/\d{2}/{$illustId}(_p0)?_square1200\.jpg~", $res->getBody(), $match); + $illustThumbnailUrl = $match[0]; - $illustUrl = $this->thumbnailToMasterUrl($illustThumbnailUrl); + $illustUrl = $this->thumbnailToMasterUrl($illustThumbnailUrl); - // 指定ページに変換 - $illustUrl = str_replace('p0_master', "p{$page}_master", $illustUrl); + $metadata->image = $this->proxize($illustUrl); - $metadata->image = $this->proxize($illustUrl); - - return $metadata; - } else { - throw new \RuntimeException("{$res->getStatusCode()}: $url"); - } + return $metadata; } else { - $client = new \GuzzleHttp\Client(); - $res = $client->get($url); - if ($res->getStatusCode() === 200) { - $ogpResolver = new OGPResolver(); - $metadata = $ogpResolver->parse($res->getBody()); - - // OGP がデフォルト画像であるようならなんとかして画像を取得する - if (strpos($metadata->image, 'pixiv_logo.gif') || strpos($metadata->image, 'pictures.jpg')) { - - // 作品ページの場合のみ対応 - if (strpos(parse_url($url)['query'], 'mode=medium')) { - preg_match("~https://i\.pximg\.net/c/128x128/img-master/img/\d{4}/\d{2}/\d{2}/\d{2}/\d{2}/\d{2}/{$illustId}(_p0)?_square1200\.jpg~", $res->getBody(), $match); - $illustThumbnailUrl = $match[0]; - - $illustUrl = $this->thumbnailToMasterUrl($illustThumbnailUrl); - - $metadata->image = $this->proxize($illustUrl); - } - } - - return $metadata; - } else { - throw new \RuntimeException("{$res->getStatusCode()}: $url"); - } + throw new \RuntimeException("{$res->getStatusCode()}: $url"); } } } diff --git a/app/Providers/ViewComposerServiceProvider.php b/app/Providers/ViewComposerServiceProvider.php index bb4f062..70bf1fd 100644 --- a/app/Providers/ViewComposerServiceProvider.php +++ b/app/Providers/ViewComposerServiceProvider.php @@ -2,7 +2,7 @@ namespace App\Providers; -use App\Http\ViewComposers\ProfileComposer; +use App\Http\ViewComposers\ProfileStatsComposer; use Illuminate\Support\Facades\View; use Illuminate\Support\ServiceProvider; @@ -15,7 +15,7 @@ class ViewComposerServiceProvider extends ServiceProvider */ public function boot() { - View::composer('components.profile', ProfileComposer::class); + View::composer('components.profile-stats', ProfileStatsComposer::class); } /** diff --git a/composer.json b/composer.json index 05edd13..be2f2b5 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,7 @@ "license": "MIT", "type": "project", "require": { - "php": ">=7.0.0", + "php": ">=7.1.0", "anhskohbo/no-captcha": "^3.0", "doctrine/dbal": "^2.9", "fideloper/proxy": "~3.3", diff --git a/database/migrations/2019_02_06_235832_add_bio_and_url_to_users.php b/database/migrations/2019_02_06_235832_add_bio_and_url_to_users.php new file mode 100644 index 0000000..d8988d4 --- /dev/null +++ b/database/migrations/2019_02_06_235832_add_bio_and_url_to_users.php @@ -0,0 +1,34 @@ +string('bio', 160)->default(''); + $table->text('url')->default(''); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('bio'); + $table->dropColumn('url'); + }); + } +} diff --git a/dist/php.d/99-xdebug.ini b/dist/php.d/99-xdebug.ini index a6e8b8c..007daa1 100644 --- a/dist/php.d/99-xdebug.ini +++ b/dist/php.d/99-xdebug.ini @@ -1,5 +1,4 @@ ; Dockerでのデバッグ用設定 zend_extension=xdebug.so xdebug.remote_enable=true -xdebug.remote_autostart=true xdebug.remote_host=host.docker.internal \ No newline at end of file diff --git a/public/css/tissue.css b/public/css/tissue.css index b571c9e..e71b920 100644 --- a/public/css/tissue.css +++ b/public/css/tissue.css @@ -15,6 +15,25 @@ overflow-x: auto; } +.tis-need-agecheck .container { + filter: blur(45px); + pointer-events: none; +} + +.container { + transition: filter .15s liner; +} + +.list-group-item.no-side-border { + border-left: none; + border-right: none; + border-radius: 0; +} + +.list-group-item.border-bottom-only:first-child { + border-top: none; +} + .list-group-item.border-bottom-only { border-left: none; border-right: none; @@ -27,4 +46,15 @@ .timeline-action-item { margin-left: 16px; +} + +.tis-global-count-graph { + height: 90px; + border-bottom: 1px solid rgba(0, 0, 0, .125); +} + +@media (min-width: 992px) { + .tis-sidebar-info { + font-size: small; + } } \ No newline at end of file diff --git a/public/js/tissue.js b/public/js/tissue.js new file mode 100644 index 0000000..dab7c34 --- /dev/null +++ b/public/js/tissue.js @@ -0,0 +1,49 @@ +// app.jsの名はモジュールバンドラーを投入する日まで予約しておく。CSSも同じ。 + +(function ($) { + + $.fn.linkCard = function (options) { + var settings = $.extend({ + endpoint: '/api/checkin/card' + }, options); + + return this.each(function () { + var $this = $(this); + $.ajax({ + url: settings.endpoint, + method: 'get', + type: 'json', + data: { + url: $this.find('a').attr('href') + } + }).then(function (data) { + var $title = $this.find('.card-title'); + var $desc = $this.find('.card-text'); + var $image = $this.find('img'); + + if (data.title === '') { + $title.hide(); + } else { + $title.text(data.title); + } + + if (data.description === '') { + $desc.hide(); + } else { + $desc.text(data.description); + } + + if (data.image === '') { + $image.hide(); + } else { + $image.attr('src', data.image); + } + + if (data.title !== '' || data.description !== '' || data.image !== '') { + $this.removeClass('d-none'); + } + }); + }); + }; + +})(jQuery); \ No newline at end of file diff --git a/resources/views/auth/register.blade.php b/resources/views/auth/register.blade.php index e84e9d4..0b8764d 100644 --- a/resources/views/auth/register.blade.php +++ b/resources/views/auth/register.blade.php @@ -12,6 +12,10 @@

新規登録


+
+

注意! Tissueでは、登録に使用したメールアドレスの Gravatar を使用します。

+

他の場所での活動と紐付いてほしくない場合、使用予定のメールアドレスにGravatarが設定されていないかを確認することを推奨します。

+
diff --git a/resources/views/components/profile-stats.blade.php b/resources/views/components/profile-stats.blade.php new file mode 100644 index 0000000..4502525 --- /dev/null +++ b/resources/views/components/profile-stats.blade.php @@ -0,0 +1,15 @@ +
現在のセッション
+@if (isset($currentSession)) +

{{ $currentSession }}経過

+

({{ $latestEjaculation->ejaculated_date->format('Y/m/d H:i') }} にリセット)

+@else +

計測がまだ始まっていません

+

(一度チェックインすると始まります)

+@endif + +
概況
+

平均記録: {{ Formatter::formatInterval($summary[0]->average) }}

+

最長記録: {{ Formatter::formatInterval($summary[0]->longest) }}

+

最短記録: {{ Formatter::formatInterval($summary[0]->shortest) }}

+

合計時間: {{ Formatter::formatInterval($summary[0]->total_times) }}

+

通算回数: {{ $summary[0]->total_checkins }}回

\ No newline at end of file diff --git a/resources/views/components/profile.blade.php b/resources/views/components/profile.blade.php index 08b9818..676764f 100644 --- a/resources/views/components/profile.blade.php +++ b/resources/views/components/profile.blade.php @@ -1,6 +1,6 @@
- +

{{ $user->display_name }}

@@ -11,22 +11,28 @@ @endif - @if (!$user->is_protected || $user->isMe()) -
現在のセッション
- @if (isset($currentSession)) -

{{ $currentSession }}経過

-

({{ $latestEjaculation->ejaculated_date->format('Y/m/d H:i') }} にリセット)

- @else -

計測がまだ始まっていません

-

(一度チェックインすると始まります)

- @endif + {{-- Bio --}} + @if (!empty($user->bio)) +

+ {!! Formatter::linkify(nl2br(e($user->bio))) !!} +

+ @endif -
概況
-

平均記録: {{ Formatter::formatInterval($summary[0]->average) }}

-

最長記録: {{ Formatter::formatInterval($summary[0]->longest) }}

-

最短記録: {{ Formatter::formatInterval($summary[0]->shortest) }}

-

合計時間: {{ Formatter::formatInterval($summary[0]->total_times) }}

-

通算回数: {{ $summary[0]->total_checkins }}回

+ {{-- URL --}} + @if (!empty($user->url)) +

+ + {{ preg_replace('~\Ahttps?://~', '', $user->url) }} +

@endif
-
\ No newline at end of file +
+ +@if (!$user->is_protected || $user->isMe()) +
+
+ @component('components.profile-stats', ['user' => $user]) + @endcomponent +
+
+@endif diff --git a/resources/views/ejaculation/checkin.blade.php b/resources/views/ejaculation/checkin.blade.php index 99d04c3..1639110 100644 --- a/resources/views/ejaculation/checkin.blade.php +++ b/resources/views/ejaculation/checkin.blade.php @@ -40,7 +40,7 @@
-
+
    diff --git a/resources/views/ejaculation/edit.blade.php b/resources/views/ejaculation/edit.blade.php index dab8924..ee1c387 100644 --- a/resources/views/ejaculation/edit.blade.php +++ b/resources/views/ejaculation/edit.blade.php @@ -41,7 +41,7 @@
    -
    +
      diff --git a/resources/views/ejaculation/show.blade.php b/resources/views/ejaculation/show.blade.php index 4a55930..b61cf00 100644 --- a/resources/views/ejaculation/show.blade.php +++ b/resources/views/ejaculation/show.blade.php @@ -112,42 +112,8 @@ form.submit(); }); - $('.link-card').each(function () { - var $this = $(this); - $.ajax({ - url: '{{ url('/api/checkin/card') }}', - method: 'get', - type: 'json', - data: { - url: $this.find('a').attr('href') - } - }).then(function (data) { - var $title = $this.find('.card-title'); - var $desc = $this.find('.card-text'); - var $image = $this.find('img'); - - if (data.title === '') { - $title.hide(); - } else { - $title.text(data.title); - } - - if (data.description === '') { - $desc.hide(); - } else { - $desc.text(data.description); - } - - if (data.image === '') { - $image.hide(); - } else { - $image.attr('src', data.image); - } - - if (data.title !== '' || data.description !== '' || data.image !== '') { - $this.removeClass('d-none'); - } - }); + $('.link-card').linkCard({ + endpoint: '{{ url('/api/checkin/card') }}' }); @endpush \ No newline at end of file diff --git a/resources/views/guest.blade.php b/resources/views/guest.blade.php index fb670f0..3b21fa6 100644 --- a/resources/views/guest.blade.php +++ b/resources/views/guest.blade.php @@ -31,6 +31,9 @@
      @foreach($informations as $info) + @if ($info->pinned) + ピン留め + @endif {{ $categories[$info->category]['label'] }} {{ $info->title }} - {{ $info->created_at->format('n月j日') }} @endforeach diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php index cdb81cb..eb55241 100644 --- a/resources/views/home.blade.php +++ b/resources/views/home.blade.php @@ -7,119 +7,142 @@
      - @component('components.profile', ['user' => Auth::user()]) - @endcomponent -
      -
      +
      +
      +
      + +
      +
      + {{ Auth::user()->display_name }} +
      +
      + @{{ Auth::user()->name }} + @if (Auth::user()->is_protected) + + @endif +
      +
      +
      + @component('components.profile-stats', ['user' => Auth::user()]) + @endcomponent +
      +
      サイトからのお知らせ
      - - @if (!empty($publicLinkedEjaculations)) -
      -
      お惣菜コーナー
      -
      -

      最近の公開チェックインから、オカズリンク付きのものを表示しています。

      -
      - +
      +
      + @if (!empty($globalEjaculationCounts)) +
      チェックインの動向
      +
      +
      @endif + @if (!empty($publicLinkedEjaculations)) +
      お惣菜コーナー
      +

      最近の公開チェックインから、オカズリンク付きのものを表示しています。

      + + @endif
      @endsection @push('script') + @endpush \ No newline at end of file diff --git a/resources/views/info/index.blade.php b/resources/views/info/index.blade.php index dd7119b..62376bb 100644 --- a/resources/views/info/index.blade.php +++ b/resources/views/info/index.blade.php @@ -9,6 +9,9 @@
      @foreach($informations as $info) + @if ($info->pinned) + ピン留め + @endif {{ $categories[$info->category]['label'] }} {{ $info->title }} - {{ $info->created_at->format('n月j日') }} @endforeach diff --git a/resources/views/info/show.blade.php b/resources/views/info/show.blade.php index 8dba829..3e0b597 100644 --- a/resources/views/info/show.blade.php +++ b/resources/views/info/show.blade.php @@ -11,7 +11,12 @@

      {{ $category['label'] }} {{ $info->title }}

      -

      {{ $info->created_at->format('Y年n月j日') }}

      +

      + @if ($info->pinned) + ピン留め + @endif + {{ $info->created_at->format('Y年n月j日') }} +

      @parsedown($info->content)
      @endsection \ No newline at end of file diff --git a/resources/views/layouts/base.blade.php b/resources/views/layouts/base.blade.php index 7ef1a5f..ed8851f 100644 --- a/resources/views/layouts/base.blade.php +++ b/resources/views/layouts/base.blade.php @@ -18,7 +18,18 @@ @stack('head') - + +
      -
      +
      @@ -26,6 +26,24 @@
      現在は変更できません。
      +
      + + + 最大 160 文字 + + @if ($errors->has('bio')) +
      {{ $errors->first('bio') }}
      + @endif +
      +
      + + + + @if ($errors->has('url')) +
      {{ $errors->first('url') }}
      + @endif +
      diff --git a/resources/views/user/profile.blade.php b/resources/views/user/profile.blade.php index 5a674fc..4e442ca 100644 --- a/resources/views/user/profile.blade.php +++ b/resources/views/user/profile.blade.php @@ -136,42 +136,8 @@ form.submit(); }); - $('.link-card').each(function () { - var $this = $(this); - $.ajax({ - url: '{{ url('/api/checkin/card') }}', - method: 'get', - type: 'json', - data: { - url: $this.find('a').attr('href') - } - }).then(function (data) { - var $title = $this.find('.card-title'); - var $desc = $this.find('.card-text'); - var $image = $this.find('img'); - - if (data.title === '') { - $title.hide(); - } else { - $title.text(data.title); - } - - if (data.description === '') { - $desc.hide(); - } else { - $desc.text(data.description); - } - - if (data.image === '') { - $image.hide(); - } else { - $image.attr('src', data.image); - } - - if (data.title !== '' || data.description !== '' || data.image !== '') { - $this.removeClass('d-none'); - } - }); + $('.link-card').linkCard({ + endpoint: '{{ url('/api/checkin/card') }}' }); @endpush \ No newline at end of file diff --git a/tests/Unit/MetadataResolver/OGPResolverTest.php b/tests/Unit/MetadataResolver/OGPResolverTest.php index fd9375f..1befcd0 100644 --- a/tests/Unit/MetadataResolver/OGPResolverTest.php +++ b/tests/Unit/MetadataResolver/OGPResolverTest.php @@ -35,4 +35,19 @@ class OGPResolverTest extends TestCase $this->assertEmpty($metadata->description); $this->assertEmpty($metadata->image); } + + public function testResolveTitleAndDescription() + { + $resolver = new OGPResolver(); + + $html = <<Welcome to my homepage + +EOF; + + $metadata = $resolver->parse($html); + $this->assertEquals('Welcome to my homepage', $metadata->title); + $this->assertEquals('This is my super hyper ultra homepage!!', $metadata->description); + $this->assertEmpty($metadata->image); + } }