423 Commits

Author SHA1 Message Date
eai04191
8f58fce1b0 🏴󠁧󠁢󠁷󠁬󠁳󠁿 2019-11-16 01:31:13 +09:00
shibafu
3420e053fc Merge pull request #297 from shikorism/fix/admin-menu-in-sp-layout
管理画面へのリンクがモバイルで表示されないバグの修正
2019-11-14 21:15:09 +09:00
shibafu
a01bc6989e 管理画面へのリンクがモバイルで表示されないバグの修正 2019-11-14 00:59:23 +09:00
shibafu
a71aa0c3b6 Merge pull request #296 from shikorism/fix/295-drop-empty-value-tag
空文字列のタグは保存せず捨てる
2019-11-13 00:39:26 +09:00
shibafu
26be8a086e 空文字列のタグは保存せず捨てる 2019-11-09 23:19:28 +09:00
shibafu
af5de3ee14 Merge pull request #294 from eai04191/feature/resolver-dlsite-affiliate-url
DLsiteResolver 新しいアフィリエイトURL構造に対応
2019-11-09 18:40:01 +09:00
eai04191
c8cee80144 冗長な評価を削除 2019-11-09 18:28:47 +09:00
eai04191
d8e170ff85 DLsiteの新しいアフィリエイトURL構造に対応 2019-11-08 05:36:40 +09:00
shibafu
9c101dfb7b Merge pull request #293 from eai04191/feature/resolver-xtube-fix
XtubeResolver 修正
2019-11-05 08:17:34 +09:00
eai04191
12fd228e75 array_mapのcallbackをシンプルにする 2019-11-05 05:59:00 +09:00
eai04191
dc0eb0a548 tagの各要素をtrim()する 2019-11-04 14:24:09 +09:00
eai04191
16ed4482f4 OGPを吐くようになっていたのでOGPResolverでimageを取得するように変更 2019-11-04 14:23:30 +09:00
eai04191
57a847baf5 Update fixture 2019-11-04 14:01:42 +09:00
shibafu
332b6d7dd0 こっちのほうがマシ 2019-10-14 02:24:39 +09:00
shibafu
99a92c6106 チェックインの編集は本人のみ可能
(cherry picked from commit bcb5abb161)
2019-10-14 02:17:02 +09:00
shibafu
4ca6f00c1b Merge pull request #288 from MitarashiDango/feature_tag_list
タグ一覧画面を追加
2019-10-11 00:33:25 +09:00
shibafu
7b8811b894 Merge pull request #281 from shikorism/fix/279-unauthorized-ajax-call
非ログイン状態でいいねしようとした時にログインを促す
2019-10-11 00:31:24 +09:00
shibafu
c7e261d06b Merge pull request #289 from shikorism/fix/nijie-unescape-after-json-decode
NijieResolver: JSONデコード後にHTMLエンティティのデコードを行う
2019-10-04 00:32:57 +09:00
shibafu
b3c98613e7 テストの追加 2019-10-03 23:34:57 +09:00
shibafu
f7a5948e8e Merge pull request #292 from kb10uy/add-kb10uyshortstoryserverresolver
Kb10uyShortStoryServerResolver の追加
2019-10-03 23:22:22 +09:00
Yuu Kobayashi
d1abca5416 成否判定を除去 2019-09-29 18:03:34 +09:00
Yuu Kobayashi
579708389a Kb10uyShortStoryServerResolver とテストを追加 2019-09-29 11:33:05 +09:00
MitarashiDango
900e4c94a7 怒られたのでなおした... 2019-09-25 23:46:06 +09:00
MitarashiDango
a434a45e4a タグ一覧の件数カウント条件を検索機能と揃える 2019-09-25 23:39:36 +09:00
shibafu
4f6bb0ac15 Merge pull request #290 from eai04191/feature/resolver-pixiv-new-url
PixivResolver 新URL形式に対応
2019-09-25 22:07:55 +09:00
eai04191
e27a848b08 Pixiv新URL形式に対応 2019-09-25 08:42:28 +09:00
shibafu
8772facadf JSONデコード後にHTMLエンティティのデコードを行う
JSONデコード前にHTMLエンティティのデコードを行ってしまうと " なども解除されてしまい、JSONとして不正な入力になる。
html_decode_entityのオプションで除外しても良いが、改行以外は一応JSONとして正当はなずなので、安全に倒してJSONとしてのデコードを済ませてから処理する。
2019-09-22 01:44:14 +09:00
MitarashiDango
d5ee59825f 年齢確認ダイアログ表示時のブラーをタグ一覧へ適用させる 2019-09-19 00:09:41 +09:00
shibafu
4192f22af5 Merge pull request #287 from shikorism/fix/seiga-partially-tag-test
NicoSeigaResolverTest: ロックされているタグのみを検証する
2019-09-18 22:23:25 +09:00
MitarashiDango
dd07940aea スマホ向けレイアウトへタグ一覧ボタンを追加 2019-09-16 14:02:19 +09:00
MitarashiDango
78bb7dae28 タグ一覧画面を追加 2019-09-16 13:24:22 +09:00
shibafu
e4890f65ae 頻繁に変更される部分を完全一致判定してもきりがないので、ロックされているタグのみを検証する 2019-09-16 09:55:29 +09:00
shibafu
2454a24ee2 Merge pull request #286 from shikorism/username-hint
ユーザー名は変更できないことを明記する
2019-09-15 02:19:09 +09:00
shibafu
7858bd0a5f ユーザー名は変更不能である旨を登録画面に明記する 2019-09-15 02:10:54 +09:00
shibafu
ce8855510c ユーザー名は変更不能です 2019-09-15 02:06:10 +09:00
shibafu
c42a3d2657 Merge pull request #285 from shikorism/fix/hide-private-like-in-like-users
いいね非公開のユーザーが、チェックイン画面のいいね件数欄に露出してしまう不具合の修正
2019-09-15 02:02:42 +09:00
shibafu
7cb6dd4754 いいね非公開のユーザーが、チェックイン画面のいいね件数欄に露出する不具合の修正 2019-09-15 01:55:17 +09:00
shibafu
bf1c4e7a21 Merge pull request #284 from eai04191/feature/resolver-fanza-improve
FanzaResolverに機能・テストを追加
2019-09-13 01:18:26 +09:00
shibafu
7a56072765 cosmetic change 2019-09-13 01:13:15 +09:00
eai04191
d6e0512dae 返り値の型を指定 2019-09-13 01:05:09 +09:00
eai04191
1edc70fc4c リンクのみをタグにするようにCSSセレクタを修正 2019-09-13 01:03:08 +09:00
shibafu
a20f690cdd Merge pull request #283 from eai04191/feature/resolver-fantiaResolver-improve
FantiaResolverを更新・テストを追加
2019-09-12 23:37:29 +09:00
shibafu
eab901d56d Merge pull request #282 from eai04191/feature/test-cienResolver
CienResolverのテストを追加
2019-09-12 23:05:58 +09:00
eai04191
8c88d60034 私が悪うございました 2019-09-12 11:58:21 +09:00
eai04191
599e3f9557 FanzaResolverに機能・テストを追加
動画、同人、電子書籍、PCゲームにてタグ取得に対応
2019-09-12 11:39:18 +09:00
eai04191
a9a0f3b99a FantiaResolverを更新・テストを追加
APIを見つけたのでAPIを使用するように
タグを追加
テストを追加
2019-09-12 06:56:28 +09:00
eai04191
5fc0c6c1b6 CienResolverのテストを追加 2019-09-12 05:26:03 +09:00
shibafu
5642e73391 401 Unauthorizedはよくないね 2019-09-12 00:00:34 +09:00
shibafu
92847fefe0 Merge pull request #280 from eai04191/feature/remove-status-200
不要な成否判定の削除
2019-09-10 23:40:21 +09:00
shibafu
178ed02d00 Merge pull request #276 from eai04191/feature/fix-resolver-deviantart
DeviantArtResolverのfixture・テストを更新
2019-09-10 23:40:01 +09:00
shibafu
3381965896 Merge pull request #270 from MitarashiDango/feature_add_okazu_spoiler
チェックインに対してセンシティブフラグを付与可能とする対応を実施
2019-09-10 23:39:45 +09:00
eai04191
dc8a70291d URL置換を削除 タグを追加
なんかもともと入っているurlが十分大きいので置換する必要がないことに気が付きました
2019-09-10 23:04:21 +09:00
shibafu
a10acdd481 モックがHTTPステータスコードに応じた例外を投げるようにする 2019-09-10 22:35:36 +09:00
shibafu
2c4eaccf43 OGPResolverTestをrevert、Guzzleの例外を期待するように変更 2019-09-10 22:34:24 +09:00
shibafu
141d5ce77c Merge pull request #278 from shikorism/feature/nicoseiga-tags
ニコニコ静画のタグを取得する & テストの追加
2019-09-10 20:20:30 +09:00
eai04191
ccade6ff9f 怒られたので直した(小学生並みの感想) 2019-09-10 07:33:29 +09:00
eai04191
033784bfc8 不要な成否判定の削除
ref: c0b76e5
2019-09-10 07:03:46 +09:00
eai04191
db39ee35c2 不要な判定の削除
ref: c0b76e5
2019-09-10 01:27:35 +09:00
shibafu
fb6c1a0574 Merge pull request #277 from eai04191/feature/test-fix-xtube
XtubeResolverの取得方法を変更・テストを更新
2019-09-10 01:05:26 +09:00
shibafu
59aec2c038 不要な成否判定をやめる 2019-09-10 01:01:47 +09:00
shibafu
d45898931a Merge pull request #274 from eai04191/feature/test-fix-dlsiteresolver
DLsiteResolverのfixture・テストを更新
2019-09-10 00:52:53 +09:00
shibafu
fb50881e74 Merge pull request #275 from eai04191/feature/test-fix-toranoanaresolver
ToranoanaResolverのfixture・テストを更新
2019-09-10 00:51:27 +09:00
shibafu
40fedf59d4 ニコニコ静画のテストの追加 2019-09-10 00:47:32 +09:00
shibafu
b2014a3db7 ニコニコ静画のサムネイルをhttpsで取得する 2019-09-10 00:47:32 +09:00
shibafu
f3a4f682a8 ニコニコ静画のタグを取得する 2019-09-10 00:47:31 +09:00
eai04191
c0b76e522b 200でなかったときのテストを削除
4xxや5xxの場合は`\GuzzleHttp\Exception\BadResponseException`が発生するので、今まであったelseのところには到達しなかった
2019-09-09 13:52:32 +09:00
eai04191
0f530099b4 データの取得方法をAPIからスクレイピングに変更
より多くのタグと高画質なサムネイルを取得するように変更
2019-09-09 13:51:07 +09:00
eai04191
eecace33bd 2桁以上のcdnのURLに対応できるように正規表現を修正 2019-09-09 10:58:20 +09:00
eai04191
d049a6f631 すべてwixmpを使用するようになったので分ける必要がなくなった
ついでに$data['thumbnail_url']なら常にオプションが付いているのでオプションを含んでいるか判別する必要がなくなった
2019-09-09 10:30:10 +09:00
eai04191
4add9a87cc Update Fixture 2019-09-09 10:20:39 +09:00
eai04191
c898487a20 Update Tests 2019-09-09 10:01:26 +09:00
eai04191
03cb2b0728 Update Toranoana Fixture 2019-09-09 09:59:08 +09:00
eai04191
3c0b65ff8c Update Tests 2019-09-09 09:47:42 +09:00
eai04191
b367009c5c Update DLsite Fixture 2019-09-09 09:35:38 +09:00
shibafu
22150d0e7a 同じオカズでチェックインする際にセンシティブフラグを継承する 2019-09-08 16:32:37 +09:00
MitarashiDango
2b98267fa8 設定項目のアイコン変更 2019-09-08 02:39:18 +09:00
MitarashiDango
a7972046ef fix stylelint error 2019-09-07 21:19:31 +09:00
MitarashiDango
524d00d0ed オーバーレイのスタイルを修正 2019-09-07 21:06:06 +09:00
MitarashiDango
06cc18565e チェックインに対してセンシティブフラグを付与可能とする対応を実施 2019-09-07 19:57:44 +09:00
shibafu
743272f8d6 Merge pull request #266 from shikorism/feature/262-update-mail-address
メールアドレスを変更可能にする
2019-09-04 23:50:07 +09:00
shibafu
784fb43ae9 Merge pull request #267 from shikorism/package-update-20190904
Package update (20190904)
2019-09-04 23:49:50 +09:00
shibafu
de23a37ab3 Update composer packages 2019-09-04 22:04:37 +09:00
shibafu
72fc84a42c Update npm packages 2019-09-04 21:56:35 +09:00
shibafu
f59aa750e4 Merge pull request #265 from shikorism/dependabot/npm_and_yarn/mixin-deep-1.3.2
Bump mixin-deep from 1.3.1 to 1.3.2
2019-09-04 20:54:18 +09:00
shibafu
66f4c45f5c メールアドレスを変更可能にする 2019-09-04 20:52:26 +09:00
dependabot[bot]
c204a7e934 Bump mixin-deep from 1.3.1 to 1.3.2
Bumps [mixin-deep](https://github.com/jonschlinkert/mixin-deep) from 1.3.1 to 1.3.2.
- [Release notes](https://github.com/jonschlinkert/mixin-deep/releases)
- [Commits](https://github.com/jonschlinkert/mixin-deep/compare/1.3.1...1.3.2)

Signed-off-by: dependabot[bot] <support@github.com>
2019-09-04 11:42:19 +00:00
shibafu
aa87a8f070 Merge pull request #261 from eai04191/feature/resolver-dlsite-affiliate
DLsiteResolver アフィリエイトURLに対応
2019-09-01 18:05:16 +09:00
Aoi Irie
ab20ca5370 Update app/MetadataResolver/MetadataResolver.php
マージをミスるって

マジ

Co-Authored-By: shibafu <shibafu528@gmail.com>
2019-09-01 16:55:37 +09:00
Aoi Irie
bd84f29a27 Merge branch 'develop' into feature/resolver-dlsite-affiliate 2019-09-01 03:43:37 +09:00
shibafu
ac2077af49 Merge pull request #259 from eai04191/feature/resolver-nijie
NijieResolver 修正など
2019-08-31 03:12:34 +09:00
shibafu
27970e3ac5 Merge pull request #260 from eai04191/feature/resolver-iwara-improve
IwaraResolver 拡張など
2019-08-31 03:12:17 +09:00
shibafu
4940b7a9ca Merge pull request #252 from MitarashiDango/fix_tag_input_android
タグ入力確定時のフォールバック処理を追加
2019-08-29 00:18:07 +09:00
eai04191
5e02a8ab7a Iwara側のURL構造変更に追従 2019-08-29 00:01:17 +09:00
eai04191
a088444626 textの引数を指定して例外を回避する 2019-08-29 00:00:03 +09:00
eai04191
8ef9a1f8f4 Update fixture 2019-08-28 23:57:23 +09:00
eai04191
150a8152a4 指摘箇所の修正 2019-08-28 23:48:53 +09:00
shibafu
5ac1bae73f Update README.md 2019-08-28 23:25:48 +09:00
Aoi Irie
1bec21f15f Merge branch 'develop' into feature/resolver-dlsite-affiliate 2019-08-28 00:31:20 +09:00
shibafu
2c1976fd2b Merge pull request #256 from eai04191/feature/fix-deviantart
DeviantArtResolverでoEmbed APIを使用するように変更
2019-08-27 21:52:35 +09:00
eai04191
13c3407a4e テストを更新 2019-08-27 21:40:48 +09:00
eai04191
91e6cea79a wixmpのURL変換の修正
常にjpgを使用する
1024pxの画像を使用する
q_が付いていない場合に対応
2019-08-27 21:36:44 +09:00
eai04191
ac40a411da クエリの前に追加するように修正 2019-08-27 20:49:04 +09:00
shibafu
e2aa47151b Merge pull request #255 from eai04191/feature/resolver-dlsite-striptags
DLsiteResolverのdescriptionをstrip_tagsに通す
2019-08-27 20:05:42 +09:00
eai04191
dd38f4e0eb アフィリエイト先が不正なURLのテストを追加 2019-08-27 00:27:37 +09:00
eai04191
17bc8cebbf アフィリエイトURLに対応 2019-08-26 23:33:52 +09:00
eai04191
4f23a9404b 名前変更 2019-08-22 07:47:42 +09:00
eai04191
b7eafd881f descriptionが存在しない場合に対応 2019-08-22 07:28:28 +09:00
eai04191
0a994884a0 作者名をタグに追加する 2019-08-22 05:24:25 +09:00
eai04191
9926cc3357 役に立たないタグを含めない 2019-08-22 05:17:23 +09:00
eai04191
5069f20b50 タグ対応, images対応, テスト更新, その他 2019-08-21 05:11:36 +09:00
eai04191
93387f1ff5 assertEquals撲滅委員会 2019-08-21 03:53:32 +09:00
eai04191
7baf51fc09 書き直し, タグ対応, テスト更新 2019-08-21 03:40:47 +09:00
shibafu
0e3878a808 Merge pull request #257 from eai04191/feature/test-pixiv--this-item-has-been-deleted--very-sad
PixivResolver 作品削除に伴うテスト差し替え
2019-08-20 22:19:15 +09:00
shibafu
4c0b245574 Merge branch 'feature/249-install-dom-crawlar' into develop 2019-08-20 20:52:57 +09:00
shibafu
0a0047c4c3 symfony/css-selector を依存関係に追加
refs #249
2019-08-20 20:52:12 +09:00
shibafu
831d1668ef Merge pull request #258 from shikorism/feature/249-install-dom-crawlar
symfony/dom-crawler を依存関係に追加
2019-08-20 20:50:10 +09:00
shibafu
51c8199283 symfony/dom-crawler を依存関係に追加
refs #249
2019-08-20 20:46:28 +09:00
shibafu
78a1bdfb30 Merge pull request #254 from shikorism/feature/php-7.3
DockerfileおよびCI環境をPHP 7.3にアップデート
2019-08-20 20:44:39 +09:00
eai04191
ceff57f9f6 作品削除に伴いテスト差し替え 2019-08-20 11:31:50 +09:00
eai04191
bc2f8662fc oEmbed APIを使用するように変更 2019-08-20 11:14:44 +09:00
eai04191
2112087e89 nbspをspaceに置換 2019-08-20 01:40:02 +09:00
eai04191
c93ccb43c8 strip_tags追加 2019-08-20 00:52:00 +09:00
shibafu
58ae1bc1c1 DockerfileおよびCI環境をPHP 7.3にアップデート 2019-08-13 17:04:06 +09:00
shibafu
f7c9e83b12 Merge pull request #251 from shikorism/schedule-ci
定期的に実際のリクエストを伴うMetadataResolverのテストを実行する
2019-08-10 12:36:31 +09:00
shibafu
a1850b666b 定期テストではMetadataResolverのテストのみを実行する 2019-08-10 12:18:36 +09:00
MitarashiDango
8594caade1 タグ入力確定時のフォールバック処理を追加 2019-08-09 00:57:14 +09:00
shibafu
dddb47f68a Merge pull request #248 from eai04191/feature/unknown-territory
Add XtubeResolver
2019-08-08 00:26:17 +09:00
shibafu
1b2b043be2 Merge pull request #246 from shikorism/feature/monthly-graph-term
月間チェックイングラフの表示期間を選択可能にする
2019-08-08 00:23:15 +09:00
shibafu
c535153e1f 定期的に実際のリクエストを伴うMetadataResolverのテストを実行する 2019-08-08 00:14:59 +09:00
eai04191
830de3a5e3 Add XtubeResolver 2019-08-06 22:44:38 +09:00
shibafu
ab46117138 対象年の表記を yyyy年 にした 2019-08-04 01:33:54 +09:00
shibafu
3c2fec21a0 月間チェックイングラフの対象年を切り替えられるようにした 2019-08-04 01:31:53 +09:00
shibafu
370d1cc01b 月間グラフのデータ整形をクライアントサイドでやらせる 2019-08-04 00:57:35 +09:00
shibafu
5517cd5fab Merge pull request #244 from shikorism/prestissimo
prestissimoのインストールをDockerfileに含める
2019-08-03 17:17:34 +09:00
shibafu
3c083a7c60 Merge pull request #243 from shikorism/fix/235-tora
ToranoanaResolverで、OGP画像の代わりになりそうなサムネ画像を取得する
2019-08-03 17:06:56 +09:00
shibafu
fa6b8b87af READMEからprestissimoのインストールを削除 2019-08-03 01:00:15 +09:00
shibafu
d290bf4107 prestissimoを予めインストールしておく 2019-08-03 00:59:53 +09:00
shibafu
b274c6bc40 Cookieを付与しなくてもデータを取得できたので、付与しないように変更 2019-08-03 00:26:12 +09:00
shibafu
018532f01f ToranoanaResolverのテストを追加 2019-08-03 00:16:21 +09:00
shibafu
e4ef935dd2 OGP画像が全て代替イメージになっていたため、サムネイルのスクレイピングを実施 2019-08-02 23:48:56 +09:00
shibafu
fb84a1d416 Merge pull request #241 from shikorism/dependabot/npm_and_yarn/lodash-4.17.15
Bump lodash from 4.17.11 to 4.17.15
2019-07-25 20:17:24 +09:00
dependabot[bot]
358580a15e Bump lodash from 4.17.11 to 4.17.15
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.11 to 4.17.15.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.11...4.17.15)

Signed-off-by: dependabot[bot] <support@github.com>
2019-07-25 11:12:45 +00:00
shibafu
000b89f380 Merge pull request #237 from eai04191/feature/card-round-fix
カードの角丸を修正
2019-07-14 01:06:51 +09:00
shibafu
c5cbad4475 Merge pull request #238 from shikorism/feature/disable-inserted-tag
入力済みのタグ候補の背景色を変更する
2019-07-14 01:06:02 +09:00
shibafu
f4abb08921 編集モードの初期表示時に入力済みタグが反映されていなかったので、現在のタグを配信させるイベントを定義 2019-07-04 22:34:28 +09:00
shibafu
38eb0348f9 入力済みのタグ候補の背景色を変更する 2019-07-04 22:33:28 +09:00
eai04191
4f5595dae0 vue側も追従 2019-07-04 21:28:54 +09:00
eai04191
733e97bc58 card-img類削除、.cardにoverflow付与 2019-07-04 21:28:26 +09:00
shibafu
c4768ded38 Revert "Merge pull request #138 from eai04191/feature/bootstrap-custom"
This reverts commit 077731495c.
2019-07-04 20:58:59 +09:00
eai04191
598d27f6b8 Revert "Merge pull request #153 from shikorism/fix/140"
This reverts commit d044b6db20.
2019-07-04 20:55:33 +09:00
shibafu
cf5e269fd8 Merge pull request #233 from eai04191/feature/lint-tests
Lint tests
2019-07-02 12:51:40 +09:00
eai04191
4747f822ab Lint tests 2019-07-02 12:44:25 +09:00
shibafu
fa171bc3d3 Merge pull request #224 from shikorism/feature/105-tag-suggest
チェックイン時にメタデータからタグをサジェストする
2019-06-29 19:17:07 +09:00
shibafu
b828a233bc Merge pull request #227 from eai04191/feature/icon-identity
Gravatarのデフォルトアイコンをretroにする
2019-06-29 17:24:17 +09:00
shibafu
bbfd6a9895 Merge pull request #226 from eai04191/feature/resolver-pixiv-fix
PixivResolverの取得画像をregularにする
2019-06-29 00:13:44 +09:00
shibafu
e1dd2b1c8f メタデータの読み込み中・読み込み失敗を表示するようにした 2019-06-28 00:27:31 +09:00
shibafu
ec4e0f3dda メタデータプレビュー専用のスタイルなので .link-card-mini はSFC内に封印 2019-06-27 23:55:36 +09:00
shibafu
0aed1d9ebe 画像を含まないメタデータの場合、サムネイルを非表示にする 2019-06-27 23:53:04 +09:00
shibafu
d8cdf218b7 Update resources/assets/sass/components/_link-card.scss
Co-Authored-By: Aoi Irie <eai@mizle.net>
2019-06-27 23:50:59 +09:00
eai04191
7c9eefe478 Gravatarのデフォルトをretroに指定 2019-06-27 13:32:20 +09:00
eai04191
810a1dbc59 PixivResolverの取得画像をregularにする 2019-06-27 00:34:53 +09:00
shibafu
a521a26aa5 Merge pull request #225 from eai04191/feature/test-komiflo
KomifloResolverのテストを追加
2019-06-27 00:21:24 +09:00
eai04191
e80acf79b7 KomifloResolverのテストを追加 2019-06-27 00:05:47 +09:00
shibafu
1b5fbfabc4 Merge pull request #223 from eai04191/feature/codecov
codecovを追加する
2019-06-26 23:32:11 +09:00
shibafu
0ad0b268bc 既に入力されているタグを二重追加しない 2019-06-26 23:23:06 +09:00
shibafu
da19806a3d ページロード時点でオカズリンクにURLが入力されている場合、すぐにメタデータを取得する 2019-06-26 23:20:53 +09:00
eai04191
4390af53f9 revert Dockerfile 2019-06-26 23:15:04 +09:00
shibafu
dd12582dba Merge pull request #189 from eai04191/feature/test-pixiv
PixivResolverのテストを追加
2019-06-26 22:32:15 +09:00
eai04191
6e81f091d1 必要のないパイプを削除 2019-06-26 15:46:35 +09:00
eai04191
c60d41427d Add codecov 2019-06-26 15:31:14 +09:00
shibafu
b8482e0e3c メタデータプレビューとタグ候補選択のコンポーネント 2019-06-26 01:44:18 +09:00
shibafu
a1cb313d4f Card APIでタグ情報も取れるようにする 2019-06-26 01:40:55 +09:00
eai04191
53f7a17b8e PixivResolverのテストを追加 2019-06-26 01:12:25 +09:00
shibafu
41039a6650 Merge pull request #214 from shikorism/feature/editorconfig
.editorconfigを追加
2019-06-24 22:01:46 +09:00
shibafu
6d4f6e47a3 Merge pull request #222 from shikorism/fix/218
ユーザー入力テキストをLinkifyする時に target="_blank" rel="noopener" を付与する
2019-06-24 22:01:13 +09:00
shibafu
47eec65101 ユーザー入力テキストをLinkifyする時に target="_blank" rel="noopener" を付与する 2019-06-24 20:19:58 +09:00
shibafu
c2da5eef9d Merge pull request #217 from eai04191/feature/update-readme
README更新
2019-06-24 20:11:16 +09:00
shibafu
94cabdc827 Merge pull request #204 from shikorism/feature/average-range-limit
平均記録を直近30チェックインから算出するように変更
2019-06-24 20:10:20 +09:00
shibafu
1e11bd3290 JSON fileのインデント幅を指定
ただしcomposer.jsonは4 spaceを使うので、据え置く
2019-06-24 20:05:30 +09:00
shibafu
f729fa7908 YAML fileのインデント幅を指定 2019-06-24 19:59:43 +09:00
shibafu
cb1b2c9902 Merge pull request #206 from eai04191/feature/resolver-steam
SteamResolverを追加
2019-06-18 23:30:32 +09:00
shibafu
9a47bc970f Merge pull request #216 from eai04191/feature/update-yarn-lock
update yarn.lock
2019-06-18 23:27:30 +09:00
shibafu
0c6cee6fcb Merge pull request #215 from eai04191/feature/stylelint-ignore
.stylelintignoreを追加
2019-06-18 23:24:26 +09:00
eai04191
5172f1cb70 chownの変更先をstorageのみにする 2019-06-18 11:18:06 +09:00
eai04191
663888dcb1 prestissimoを使用してインストールを高速化 2019-06-18 11:16:51 +09:00
eai04191
762c232aef update yarn.lock 2019-06-18 11:14:11 +09:00
eai04191
e2332c7fe6 .stylelintignoreを追加 2019-06-17 07:10:08 +09:00
eai04191
16e5341de1 assertStringStartsWithに変更
モックを使わないテストがコケることがあるので
2019-06-17 04:29:54 +09:00
shibafu
8c5a7f4d09 .editorconfigを追加 2019-06-16 23:08:45 +09:00
shibafu
fe09f769e3 テストを追加 2019-06-16 22:36:00 +09:00
shibafu
85012e13de Merge remote-tracking branch 'eai/feature/resolver-dlsite-regex' into develop
# Conflicts:
#	app/MetadataResolver/MetadataResolver.php
2019-06-16 19:46:51 +09:00
shibafu
f19f970bd9 Merge pull request #212 from eai04191/feature/resolver-dlsite-fix-209
DLsiteResolverで予告作品に対応
2019-06-16 19:44:39 +09:00
shibafu
d6127a7268 Merge pull request #211 from eai04191/feature/fix-210
composer fixでconfigを指定する
2019-06-16 19:43:51 +09:00
eai04191
073ed7e618 composer fixでconfigを指定する 2019-06-16 05:52:32 +09:00
eai04191
32c1d3ff9d 予告作品に対応 2019-06-16 05:50:22 +09:00
eai04191
ede45ee4e1 twitterリンクの正規表現を修正
howtw, cowtw, sowtw, maatw, bowtw, prwtw 等があるため
2019-06-16 05:24:33 +09:00
eai04191
fbecd97c03 いい加減push前にフォーマットする習慣をつけろ 2019-06-16 04:58:38 +09:00
eai04191
9306a4376c 200でもエラーの時があるのでsuccessも見るようにする
やり方これであってるのか?
2019-06-16 04:58:38 +09:00
eai04191
c061a51f8f SteamResolverを追加 2019-06-16 04:58:38 +09:00
shibafu
b510ea4042 Merge pull request #205 from eai04191/feature/pixiv-api
PixivResolverの修正(2戦目)
2019-06-15 20:04:27 +09:00
shibafu
e004a6bced Merge pull request #208 from eai04191/feature/lint-staged-php
commit時にphp-cs-fixer fixするようにする
2019-06-15 18:57:05 +09:00
eai04191
b6864c6fc4 lint-stagedにphp-cs-fixerを追加する 2019-06-15 07:50:37 +09:00
eai04191
a45d5cd558 シリーズに対応
APIがわからないのでとりあえずOGPとcatだけ fix #172
2019-06-15 05:37:27 +09:00
eai04191
5f1a6291f7 pixivのajax APIを利用するように変更 2019-06-14 11:08:57 +09:00
shibafu
ac91e36246 Merge pull request #203 from eai04191/feature/dlsite-tags
DLsiteResolverでタグを取得する
2019-06-13 00:35:49 +09:00
shibafu
709f10f098 平均記録を直近30チェックインから算出するように変更 2019-06-12 00:03:18 +09:00
eai04191
d1da60693a タグを取得するのとテストを追加 2019-06-08 01:55:26 +09:00
eai04191
b8f7e5e6ef update fixture 2019-06-08 01:46:50 +09:00
shibafu
d926a9ea0d Test FixtureをGitHubの言語判定に含めないようにする 2019-06-08 01:16:33 +09:00
shibafu
65b0893f47 Merge pull request #199 from shikorism/feature/vue
タグ入力欄のVue化
2019-06-08 01:12:41 +09:00
shibafu
b2301444c4 Merge pull request #202 from shikorism/fix/200
iOS対策 カード省略グラデーションの透明色指定をtransparentからrgba()に変更
2019-06-07 23:12:00 +09:00
shibafu
e5e0ce08da iOS対策で、透明色の指定をtransparentからrgbaで透明な白に変更 2019-06-07 22:10:32 +09:00
shibafu
17ce2784a7 Merge pull request #187 from eai04191/fix/186
DLsiteResolverにてmakerに一致するテキストを探すように修正
2019-06-07 22:03:29 +09:00
shibafu
d470193662 Merge pull request #201 from shikorism/dependabot/npm_and_yarn/js-yaml-3.13.1
Bump js-yaml from 3.12.1 to 3.13.1
2019-06-07 01:45:59 +09:00
dependabot[bot]
ea93cc68fa Bump js-yaml from 3.12.1 to 3.13.1
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 3.12.1 to 3.13.1.
- [Release notes](https://github.com/nodeca/js-yaml/releases)
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/3.12.1...3.13.1)

Signed-off-by: dependabot[bot] <support@github.com>
2019-06-06 16:42:37 +00:00
shibafu
12dac5916e Merge pull request #177 from shikorism/feature/137-card-max-height
サムネイルを含むカードを高さ400pxに固定し、トリミング表示されるように変更
2019-06-07 01:41:53 +09:00
shibafu
f47d3454f6 Merge pull request #188 from eai04191/feature/env-mock
.env.exampleにTEST_USE_HTTP_MOCKを追加
2019-06-07 01:26:45 +09:00
shibafu
34cb3a1415 is-invalid classのこと忘れてた 2019-06-05 00:50:47 +09:00
shibafu
9471683741 タグ用のhidden inputもComponent内に含めるようにした 2019-06-05 00:46:30 +09:00
shibafu
c7d88076fa インラインスタイルをスコープCSSに追い出した 2019-06-05 00:36:41 +09:00
shibafu
0670cb8736 タグ入力欄だけVue化 2019-06-05 00:06:30 +09:00
shibafu
7a95e0979e TypeScriptの音ォ〜! 2019-06-05 00:06:29 +09:00
shibafu
333f39c9f4 Vue.jsを依存関係に追加 2019-06-05 00:06:20 +09:00
shibafu
5340d8ead9 Merge pull request #194 from shikorism/dependabot/npm_and_yarn/webpack-bundle-analyzer-3.3.2
Bump webpack-bundle-analyzer from 3.1.0 to 3.3.2
2019-06-01 01:07:55 +09:00
dependabot[bot]
348096ecee Bump webpack-bundle-analyzer from 3.1.0 to 3.3.2
Bumps [webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer) from 3.1.0 to 3.3.2.
- [Release notes](https://github.com/webpack-contrib/webpack-bundle-analyzer/releases)
- [Changelog](https://github.com/webpack-contrib/webpack-bundle-analyzer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/webpack-bundle-analyzer/compare/v3.1.0...v3.3.2)
2019-05-31 15:47:46 +00:00
shibafu
35cc0c6357 Merge pull request #193 from shikorism/fix/pixiv-resolver-tag-filter
PixivResolverのタグ抽出がおそらく「R-18」を無視できていないのを修正
2019-06-01 00:42:48 +09:00
shibafu
f0cb07c6f5 おそらく「R-18」を無視できていないのを修正 2019-05-30 23:24:33 +09:00
eai04191
11ddb83424 テストの変更漏れを修正 2019-05-03 11:58:31 +09:00
eai04191
02367646a1 makerをOGPタイトルに含まれる著者名などから取得するように変更 2019-05-03 11:58:31 +09:00
eai04191
241bcd6548 テストをたくさん追加 2019-05-03 11:58:31 +09:00
eai04191
f7c8a3010d []を含むタイトルに対応 2019-05-03 11:58:31 +09:00
eai04191
c6a32da97e girlsに対応 2019-05-03 11:58:31 +09:00
eai04191
50ff2efaaa テストを修正 2019-05-03 11:58:31 +09:00
eai04191
a958ccaa08 makerに一致するテキストを探すように修正 2019-05-03 11:58:31 +09:00
shibafu
ce3ef3cdff Merge pull request #190 from eai04191/fix/164
DLsiteResolverの正規表現を修正
2019-05-03 11:05:22 +09:00
shibafu
f9ea9cc566 Merge pull request #191 from shikorism/update/jquery-3.4.1
jQuery 3.4.1にアップデート
2019-05-03 11:00:11 +09:00
shibafu
debc350b12 Merge pull request #185 from shikorism/feature/metadata_tag
MetadataResolverからタグ情報を保存できるようにする
2019-05-03 11:00:01 +09:00
shibafu
d7f39fcc5a jQuery 3.4.1にアップデート 2019-05-03 10:25:05 +09:00
eai04191
8e6b96fb83 正規表現を修正 2019-05-03 09:36:59 +09:00
eai04191
c20bd0ca77 .env.exampleにTEST_USE_HTTP_MOCKを追加 2019-05-03 02:14:58 +09:00
shibafu
c1330ff6e3 PixivResolver: meta[name=keywords] からタグっぽい情報を抽出して保存 2019-04-29 16:06:40 +09:00
shibafu
085afd3318 KomifloResolver: 作者情報と作品タグをタグとして保存 2019-04-29 14:51:53 +09:00
shibafu
a8a6aecef3 Merge pull request #184 from MitarashiDango/fix_ime_conversion
特定条件下においてタグ入力が正常に行えない問題の修正
2019-04-29 14:32:54 +09:00
shibafu
e4c942263a MetadataResolverからタグ情報を保存できるようにした 2019-04-29 12:43:05 +09:00
shibafu
1322e89b86 Metadata - Tags の多対多リレーション追加 2019-04-29 11:40:17 +09:00
MitarashiDango
dbafe2c75e 特定条件下においてタグ入力が正常に行えない問題の修正 2019-04-25 03:11:11 +09:00
shibafu
91c786199a アナル締めた 2019-04-21 17:12:18 +09:00
shibafu
8de5fa891e Merge pull request #181 from shikorism/fix/private-like
非公開チェックインに対するいいね関係のバグ修正
2019-04-21 02:16:47 +09:00
shibafu
464b690fbd 非公開チェックインのいいねに「非公開」の表記がなかったので付けた 2019-04-21 02:14:08 +09:00
shibafu
d02b65e4ed 非公開チェックインのいいねが他人に見えてしまっていた不具合の修正 2019-04-21 02:12:42 +09:00
shibafu
51485a8ac1 Merge pull request #179 from shikorism/fix/178-tora-url
ToranoanaResolverのマッチ対象に電子書籍と女性向け通販を追加
2019-04-20 23:49:34 +09:00
shibafu
59a6aa869f ToranoanaResolverのマッチ対象に電子書籍と女性向け通販を追加 2019-04-20 22:38:36 +09:00
shibafu
b3ea665f0b Merge pull request #176 from shikorism/fix/174-ap-empty-content
ActivityPubResolverでcontentが空のときはHTMLとしてのパースを行わないようにする
2019-04-17 23:38:52 +09:00
shibafu
f791fd8fbd Merge pull request #160 from eai04191/feature/readme-phpunit
READMEに phpunit の使い方を追記
2019-04-17 23:38:23 +09:00
shibafu
676793cfe5 Merge pull request #173 from shikorism/koresuki
チェックインに対する「いいね」機能
2019-04-17 19:55:42 +09:00
shibafu
2ff07cc68d サムネイルを含むカードを高さ400pxに固定、サムネイルはトリミング表示されるようにした 2019-04-14 23:16:05 +09:00
shibafu
f95f1592f7 contentが空のときはHTMLとしてのパースを行わないようにする 2019-04-14 17:40:31 +09:00
shibafu
d655f24fbf コメントの手直し 2019-04-14 16:47:58 +09:00
shibafu
b80d74bae1 いいねしたユーザーの表示を直近10ユーザーのみに絞りこむ 2019-04-14 16:46:20 +09:00
shibafu
9b95f3a8b8 eloquent-eager-limit を依存関係に追加 2019-04-14 16:39:14 +09:00
shibafu
859a186acb 詳細画面ではいいねユーザーのアイコンを少し大きく表示 2019-04-14 02:18:38 +09:00
shibafu
fca6b6e98b いいねユーザー表示の文字部分の幅が縮まないようにした 2019-04-14 02:16:33 +09:00
shibafu
26b52e1c87 Merge pull request #175 from eai04191/feature/icon-preview-in-setting
プロフィール設定にアイコンプレビューと登録メールアドレスの欄を追加する
2019-04-13 22:18:43 +09:00
eai04191
f895996b18 phpunit の使い方を追記 2019-04-13 18:36:21 +09:00
eai04191
f19621c04a プロフィール設定に登録メールアドレスを表示 2019-04-13 18:21:21 +09:00
eai04191
e8cfc48417 プロフィール設定にアイコンプレビューとGravatarへのリンクを追加 2019-04-13 18:20:12 +09:00
shibafu
566d288395 いいねタブが常に表示されるように変更 2019-04-09 23:09:59 +09:00
shibafu
ddac533539 いいね一覧の公開設定を追加 2019-04-09 23:09:59 +09:00
shibafu
c20a8066c9 生クエリ/// 2019-04-09 23:09:59 +09:00
shibafu
664448b9fc いいね秘匿用のフラグ users.private_likes の追加 2019-04-09 23:09:59 +09:00
shibafu
e9c1726567 いいねユーザー表示の項目表示をアイコンから文字列に変更 2019-04-07 23:44:03 +09:00
shibafu
db7dce5830 Scssファイル分割 2019-04-07 23:30:03 +09:00
shibafu
4ed2a9048d いいねしたユーザーの一覧を表示 2019-04-07 23:27:24 +09:00
shibafu
2cd09402d1 チェックインに対する操作ボタンの位置を変更 2019-04-07 22:50:03 +09:00
shibafu
3cba46bff0 Merge pull request #170 from shikorism/fix/visibility-issue-on-search
チェックイン非公開ユーザーのチェックインが検索結果として表示される問題の修正
2019-04-07 00:13:16 +09:00
shibafu
9cec184d21 チェックイン非公開ユーザーのチェックインが検索結果として表示される問題の修正 2019-04-07 00:10:01 +09:00
shibafu
1b4f621191 fix style 2019-04-06 00:28:44 +09:00
shibafu
5bafe9126a ヘッダーメニューにいいね一覧へのリンクを追加 2019-04-06 00:15:54 +09:00
shibafu
fe880ee599 いいね一覧の追加 2019-04-06 00:14:05 +09:00
shibafu
a15716bb54 Merge pull request #167 from eai04191/develop
画像の最適化
2019-04-05 23:22:26 +09:00
shibafu
02f03165d6 ボタン部分のスマートフォンにおける表示を改善 2019-04-05 23:11:33 +09:00
shibafu
225d0854ef いいねボタンの追加 2019-04-05 23:11:20 +09:00
shibafu
34b7cd6c89 Like APIの追加 2019-04-05 23:10:44 +09:00
Eai
199e5dac05 Merge pull request #14 from eai04191/imgbot
[ImgBot] Optimize images
2019-04-02 03:12:58 +09:00
ImgBotApp
062034fb83 [ImgBot] Optimize images
*Total -- 14.84kb -> 11.65kb (21.49%)

/public/chrome-touch-icon-192x192.png -- 7.71kb -> 6.05kb (21.58%)
/public/apple-touch-icon.png -- 7.12kb -> 5.60kb (21.4%)
2019-04-01 16:43:37 +00:00
shibafu
212fad4d66 Merge pull request #166 from shikorism/fix/exclude-future-checkin-from-stats
グラフ画面で表示するデータ範囲を当月末までに制限
2019-03-31 09:55:40 +09:00
shibafu
04d7116eca Merge pull request #163 from shikorism/fix/128-bdi-name
チェックインビュー上のdisplay_nameをbdiタグでマークアップ
2019-03-31 09:52:28 +09:00
shibafu
cc8ee2e520 グラフ画面で表示するデータ範囲を当月末までに制限 2019-03-31 09:46:28 +09:00
shibafu
04f1a344a0 チェックインビュー上のdisplay_nameをbdiタグでマークアップ 2019-03-26 22:25:39 +09:00
shibafu
379d4563c5 Merge pull request #162 from unarist/theme-color
manifest.jsonにテーマカラーを追加
2019-03-26 22:16:22 +09:00
unarist
ca2aeea4f5 manifest.jsonにテーマカラーを追加 2019-03-22 00:29:52 +09:00
shibafu
0e45d27295 Merge pull request #147 from eai04191/feature/theme-color
プライマリカラーを変更
2019-03-19 22:52:14 +09:00
shibafu
8e161252a7 Merge pull request #158 from shikorism/feature/xdebug-for-linux
Linux+Dockerな開発環境でxdebugが使えるようにコールバック先IPアドレスの設定を調整
2019-03-19 22:45:43 +09:00
shibafu
a7859fdda6 set -eしてるんだから落ちるだろうが 2019-03-19 00:56:31 +09:00
shibafu
34df704fdb host.docker.internalが使える場合は使う 2019-03-19 00:35:23 +09:00
shibafu
80fe53cf20 Linux+Dockerな開発環境でxdebugが使えるように、コールバック先IPアドレスの設定を調整 2019-03-18 23:22:28 +09:00
shibafu
b8ceac51f7 Merge pull request #156 from shikorism/feature/admin
お知らせ管理機能 (+ 管理者用画面)
2019-03-17 20:14:41 +09:00
shibafu
14b57bf9f5 fix DLsiteResolver test
refs #132
2019-03-17 20:07:44 +09:00
shibafu
40fdb587d7 Merge pull request #132 from eai04191/feature/resolver-dlsite-format
DLsiteResolverで余分な説明文を削除・整形する
2019-03-17 20:04:18 +09:00
shibafu
1fd43b5e38 お知らせ登録・編集のバリデーションエラー表示 2019-03-16 12:29:33 +09:00
shibafu
a998d7132f バリデーションをFormRequestで行う 2019-03-16 12:22:07 +09:00
shibafu
74c8a1b6cb 改行コードの正規化を削除 2019-03-16 10:45:51 +09:00
shibafu
e6b333eea4 Merge remote-tracking branch 'origin/feature/normalize-line-ending' into feature/admin 2019-03-16 10:45:02 +09:00
shibafu
285e529aea Merge pull request #134 from eai04191/feature/resolver-melonbooks-format
MelonbooksResolverで余分な説明文を削除・整形する
2019-03-16 10:44:13 +09:00
shibafu
a72190eda5 Merge pull request #154 from shikorism/feature/normalize-line-ending
改行コードを正規化する処理をミドルウェア化
2019-03-16 09:51:21 +09:00
shibafu
ea2db88de3 Merge pull request #155 from hinaloe/feature/webpack-docker
Dockerにnodeを追加する
2019-03-16 09:51:11 +09:00
eai04191
54477bb214 余分な文の削除・整形 2019-03-16 05:36:49 +09:00
hina
176ccef20f mix周りのREADME草案 2019-03-14 21:21:31 +09:00
hina
dd4837ef7b とりあえずDockerfileにnodeを追加 2019-03-14 20:45:01 +09:00
shibafu
9e786a5469 テストを追加 2019-03-14 00:59:19 +09:00
shibafu
df3826a6d4 コントローラ内で実装されていた改行コードの正規化を削除 2019-03-14 00:47:52 +09:00
shibafu
a35b58eb47 改行コードを正規化する処理をミドルウェア化 2019-03-14 00:43:31 +09:00
shibafu
27fc5ee6e8 fix style 2019-03-14 00:10:09 +09:00
shibafu
be700ab81b お知らせ登録・編集・削除を実装 2019-03-14 00:10:09 +09:00
shibafu
53ac4c9b8f お知らせ編集画面の表示のみ実装 2019-03-14 00:10:09 +09:00
shibafu
e69adbfbc3 ダッシュボード画面の追加 2019-03-14 00:10:09 +09:00
shibafu
f5fab4b3c1 管理者権限ゲートの定義 2019-03-14 00:10:08 +09:00
shibafu
82ccd623a6 管理者昇格/降格 CLIコマンド 2019-03-14 00:10:08 +09:00
shibafu
e98ed0c3ca users.is_admin 列の追加 2019-03-13 23:25:39 +09:00
shibafu
2dd5cbd072 Merge pull request #149 from eai04191/feature/resolver-fc2contents
FC2ContentsResolverを追加
2019-03-13 22:57:45 +09:00
shibafu
d044b6db20 Merge pull request #153 from shikorism/fix/140
カード画像の角丸の位置をメディアクエリで可変にした
2019-03-13 20:40:31 +09:00
shibafu
8e366870b1 カード画像の角丸の位置をメディアクエリで可変にした 2019-03-12 20:44:26 +09:00
shibafu
54eec1a861 Merge pull request #152 from shikorism/fix/151
タグ検索結果から編集・削除ボタンを消す
2019-03-12 20:35:16 +09:00
shibafu
8a39feff29 タグ検索結果から編集・削除ボタンを消す
fix #151
2019-03-12 20:32:47 +09:00
shibafu
8a8ca1a26e Merge pull request #148 from shikorism/fix/145-show-delete-modal-manually
削除モーダルの初期化が完了してから削除ボタンクリックイベントを仕掛ける
2019-03-12 19:48:05 +09:00
shibafu
e262b27d0d Merge pull request #150 from eai04191/feature/remove-time-bomb
アラートの自動削除を削除
2019-03-12 19:47:49 +09:00
eai04191
bd1976c4cf アラートの自動削除を削除
close #146
2019-03-12 03:44:35 +09:00
eai04191
3fa2d80507 Fix style 2019-03-12 03:38:05 +09:00
eai04191
ec5a78db38 FC2ContentsResolverのテストを追加 2019-03-12 03:31:16 +09:00
eai04191
2db45951f4 FC2ContentsResolverを追加 2019-03-12 03:30:48 +09:00
shibafu
00a819de23 削除モーダルの初期化が完了してから削除ボタンクリックイベントを仕掛ける 2019-03-11 22:55:32 +09:00
eai04191
4f559cd1d4 プライマリカラーを変更 2019-03-11 21:26:34 +09:00
shibafu
16005931fc fix style 2019-03-10 21:49:23 +09:00
shibafu
a58811a8fe Merge pull request #144 from shikorism/fix/stylelint-args
stylelintの対象ファイルパターンを修正
2019-03-10 21:46:59 +09:00
shibafu
89c0b39755 stylelintの対象ファイルパターンを修正 2019-03-10 21:38:50 +09:00
shibafu
1fb4352e48 Merge branch 'feature/stylelint' into develop 2019-03-10 20:44:23 +09:00
shibafu
ad747577e4 fix style 2019-03-10 20:43:38 +09:00
eai04191
a2ee4ef505 huskyとlint-stagedを追加してコミット前にcssをlintする 2019-03-10 20:00:58 +09:00
eai04191
5d1ffca1ee CircleCIにstylelintによるlintを追加 2019-03-10 20:00:41 +09:00
shibafu
5c612d7eef Merge pull request #118 from eai04191/feature/plurk
PlurkResolverを追加
2019-03-10 18:52:58 +09:00
shibafu
cf0d370e61 fix style 2019-03-10 18:52:03 +09:00
shibafu
91ec1c391e Merge pull request #141 from eai04191/feature/bootstrap-4.3.0
Bootstrap 4.3.1にアップデート
2019-03-10 18:33:36 +09:00
eai04191
83d0fb3a7a npm scriptを追加
npm run stylelintで実行可能
2019-03-10 18:22:16 +09:00
eai04191
b8f7b5dfe0 stylelint-config-recess-orderを導入 2019-03-10 18:21:15 +09:00
eai04191
51fc65e66f Stylelintを導入 2019-03-10 18:10:20 +09:00
eai04191
a674195db9 もっと見るリンクをStretched linkにする
リンク範囲が広いとうれしい
2019-03-10 17:58:29 +09:00
shibafu
1a7b6c8f3c Merge pull request #142 from eai04191/feature/gitignore-vscode
/.vscodeを.gitignoreに追加
2019-03-10 17:35:09 +09:00
shibafu
077731495c Merge pull request #138 from eai04191/feature/bootstrap-custom
Bootstrapのカスタムcssを別のファイルに移動する
2019-03-10 17:33:29 +09:00
eai04191
7190367936 /.vscodeを.gitignoreに追加 2019-03-10 16:41:22 +09:00
eai04191
e1eb359887 .tis-word-wrapを廃止して.text-breakを使用する 2019-03-10 16:28:38 +09:00
eai04191
57b10d98ac Bootstrap 4.3.1にアップデート 2019-03-10 16:16:47 +09:00
eai04191
7c70e6db7e typo修正 2019-03-10 15:41:34 +09:00
eai04191
b430ba7162 partialにする 2019-03-10 15:36:55 +09:00
shibafu
d9d6c10a34 Merge pull request #136 from shikorism/fix/87-meta-desc-line-break
メタデータのdescriptionに含まれる改行を表示に反映する
2019-03-10 15:36:08 +09:00
shibafu
1a7d958a1e Sass入ってるのに生CSSで書こうとしたやつがいるらしい 2019-03-09 17:07:00 +09:00
eai04191
e983a3da0b border-radiusのmixinを使用する 2019-03-09 03:49:47 +09:00
eai04191
ed1cfe94f0 Bootstrapのカスタムを別のファイルに移動する 2019-03-09 03:42:45 +09:00
shibafu
cd56e1bff3 メタデータのdescriptionに含まれる改行を表示に反映する
refs #87
2019-03-09 01:34:58 +09:00
shibafu
5db60a4524 Merge pull request #133 from hinaloe/feature/extract-vendorjs
複数のエントリポイントから共通して利用されているベンダモジュールを外出し
2019-03-08 23:20:59 +09:00
shibafu
3f0171fa8b Merge pull request #131 from eai04191/feature/resolver-fanza-tag-fix
FanzaResolverでdescriptionにある不要な`<>`を削除する
2019-03-08 23:05:13 +09:00
shibafu
61498133d5 fix style 2019-03-08 23:02:24 +09:00
shibafu
088901fec7 Merge pull request #129 from eai04191/feature/dlsite-resolver-shortlink
DLsiteResolverをdlsite.jpの形式に対応させる
2019-03-08 22:58:53 +09:00
shibafu
dab1732a1d Merge pull request #127 from eai04191/feature/patreon-esolver-fix
PatreonResolverにてtoken-timeがある場合のみexpires_atを指定するように変更
2019-03-08 22:54:10 +09:00
shibafu
0b87a35fba fix style 2019-03-08 22:52:42 +09:00
hina
5561f0785c 中途半端に重複してたのをまとめる 2019-03-08 08:49:29 +09:00
hina
1ba7df6e82 versioning script 2019-03-08 06:54:13 +09:00
hina
94c19235b6 複数のエントリポイントから共通して利用されているベンダモジュールを外出し 2019-03-08 06:53:13 +09:00
hina
e8438a78a1 Add bundle analyzer 2019-03-08 06:52:29 +09:00
eai04191
da0fc3f3bf テストを修正 2019-03-08 06:29:33 +09:00
eai04191
d561ee66c2 trim追加 2019-03-08 06:29:16 +09:00
eai04191
d571ff1a5b 余分な文を削除・整形する 2019-03-08 06:21:33 +09:00
eai04191
a2f0beb3cb descriptionにある不要な<>を削除する 2019-03-08 05:31:01 +09:00
eai04191
41778844b8 dlsite.jpの形式に対応 2019-03-08 05:12:03 +09:00
eai04191
b29bb23b40 PlurkResolverのテストを追加 2019-03-08 03:58:45 +09:00
eai04191
a364de7d03 token-timeがある場合のみexpires_atを指定するように変更 2019-03-08 03:41:04 +09:00
shibafu
ddd2a05607 Merge pull request #125 from shikorism/feature/mix
Laravel Mix
2019-03-05 22:07:36 +09:00
shibafu
f25312a987 テスト前にmixのビルドを行う 2019-03-05 20:34:08 +09:00
shibafu
f7d9c5c1e6 にゃーん 2019-03-05 20:25:06 +09:00
shibafu
2e1ec0dfc7 Remove comment 2019-03-05 01:27:37 +09:00
shibafu
f1c56bce83 チェックイン登録/編集画面のインラインスクリプトをcheckin.jsに移動 2019-03-05 01:26:17 +09:00
shibafu
0ceb0fcf21 チェックイン削除モーダルのセットアップは共通処理に吸収 2019-03-05 01:23:14 +09:00
shibafu
37eaefc016 プライバシー設定画面のインラインスクリプトをsetting/privacy.jsに移動
これjQuery依存なくしてインラインのままでも良いような...
2019-03-05 01:17:49 +09:00
shibafu
db09bf40a6 整理しやすくするため、$.linkCardの呼出を削除 2019-03-05 01:15:00 +09:00
shibafu
1348054858 グラフ画面のデータ参照を少しだけマシにした 2019-03-05 01:11:42 +09:00
shibafu
2c396da84e グラフ画面のインラインスクリプトをuser/stats.jsに移動 2019-03-05 01:11:42 +09:00
shibafu
d4d98db686 ホーム画面のインラインスクリプトをhome.jsに移動 2019-03-05 01:11:42 +09:00
shibafu
81e37034ce ベーステンプレートのインラインスクリプトをapp.jsに移動 2019-03-05 01:11:41 +09:00
shibafu
8cdf086296 /public に直接コミットされていたファイルを削除 2019-03-05 01:11:41 +09:00
shibafu
7f087bf446 ベーステンプレートのアセット参照を貼り替え 2019-03-05 01:11:41 +09:00
shibafu
6a1848e311 既存のファイル化されているフロントコードを全てassetsに移動し、mixビルドの準備 2019-03-05 01:11:41 +09:00
shibafu
70f007b6d2 npmの依存関係を整理 2019-03-05 01:11:41 +09:00
shibafu
b7701f39dd Laravel Mix v4.0系に更新 2019-03-05 01:11:41 +09:00
shibafu
7a421d648d Merge pull request #124 from shikorism/feature/public-timeline
お惣菜コーナーをもっとたくさん見る画面
2019-03-05 01:09:15 +09:00
shibafu
8852c6b45b お惣菜コーナーの派生なので削除・修正ボタンは置かない 2019-03-04 23:21:04 +09:00
shibafu
cf5bf2b274 お惣菜コーナーをもっとたくさん見る画面 2019-03-04 23:00:15 +09:00
shibafu
e925a964f1 Merge pull request #119 from eai04191/test/DLsiteResolver
DLsiteResolverのテストを追加
2019-03-04 20:32:41 +09:00
shibafu
e966995dea fix style 2019-03-03 00:29:43 +09:00
shibafu
f21e58650d Merge pull request #122 from shikorism/feature/circleci
CircleCIの設定ファイルを追加
2019-03-03 00:26:43 +09:00
shibafu
70fed1ddf0 Merge pull request #103 from unarist/feat/web-share-target
Web Share Target 実装(のためにPWA対応)
2019-03-02 22:00:29 +09:00
unarist
c229947080 Web Share Target 実装(のためにPWA対応) 2019-03-02 20:00:52 +09:00
shibafu
08432c847e Add CircleCI Config 2019-03-02 18:55:04 +09:00
shibafu
b961956b63 Merge pull request #117 from eai04191/feature/card-horizontal
カードのレイアウトを横向きに
2019-03-02 16:04:00 +09:00
shibafu
023857f7d7 Merge pull request #121 from shikorism/feature/favicon
faviconを作った
2019-03-02 15:56:51 +09:00
shibafu
5276e8d452 16x icoを書き起こす元気はないが、縮小したやつの質が悪いので消した 2019-03-02 14:52:58 +09:00
shibafu
3cba54946a faviconを作った 2019-03-02 14:24:43 +09:00
shibafu
e6fe522f6d laravel/laravel:5.5からTrustProxiesを輸入 (#120) 2019-03-02 11:06:08 +09:00
eai04191
7eeb0207de DLsiteResolverのテストを追加 2019-03-01 14:17:01 +09:00
eai04191
1beb411050 PlurkResolverを追加 2019-03-01 03:49:01 +09:00
shibafu
eb97f01c79 Merge pull request #113 from hinaloe/fix/issue-110
コンテナ使っていないコンストラクタが残ってた fix #110
2019-03-01 00:13:50 +09:00
eai04191
0ff253cbfb cardのレイアウトを横向きに 2019-02-28 00:00:23 +09:00
shibafu
579bd3e31f Merge pull request #108 from eai04191/feature/better-mobile-navbar
モバイルのナビメニューを整える
2019-02-27 23:46:22 +09:00
shibafu
c80bb69568 Merge pull request #114 from eai04191/feature/card-component
リンクカードをコンポーネント化
2019-02-27 23:22:27 +09:00
eai04191
3c6dfeec1c cardコンポーネントの名前をlink-cardに変更 2019-02-27 22:52:45 +09:00
Eai
30c861d18c t.komiflo.comを利用したサムネイルの取得を追加 (#106) 2019-02-26 22:55:55 +09:00
Hinaloe
746db0474a add mailcatcher (#112) 2019-02-26 22:50:47 +09:00
Hinaloe
307e578d4a ルーターにコールバックで直接登録されてるルートをコントローラーに移動 (#111) 2019-02-26 22:46:40 +09:00
shibafu
be6151f0c7 SP表示のアカウントメニューが画面外に突き抜けないようにした 2019-02-26 22:32:32 +09:00
shibafu
c9cdd26728 非ログイン時にはアカウントメニューを出力しない
ログイン情報がないのに参照してコケていた
2019-02-26 22:26:22 +09:00
Eai
d0dd2db159 概況の通算回数をnumber_formatでフォーマット (#115) 2019-02-26 21:59:47 +09:00
Eai
ea12f8c9a6 typoを修正 (#109) 2019-02-26 21:51:17 +09:00
hina
a14b49b7d2 コンテナ使っていないコンストラクタが残ってた fix #110 2019-02-23 16:56:13 +09:00
eai04191
de740082b7 不要な.form-inlineで幅が制限されていたのを修正 2019-02-23 06:40:37 +09:00
eai04191
af964ad82f モバイルのナビメニューを整える
#107

Preview:

https://stellaria.network/system/media_attachments/files/000/070/479/original/0991a41baca49811.png
https://stellaria.network/system/media_attachments/files/000/070/527/original/4ace1b1df83bc380.png
2019-02-23 05:29:45 +09:00
214 changed files with 83912 additions and 10212 deletions

166
.circleci/config.yml Normal file
View File

@@ -0,0 +1,166 @@
version: 2.1
executors:
build:
docker:
- image: circleci/php:7.3-node-browsers
environment:
APP_DEBUG: true
APP_ENV: testing
APP_KEY: base64:f2tcw34GKT8EOtb5myZxJ8QLdgNivmyPhoQIPY2YfK8=
DB_CONNECTION: pgsql
DB_DATABASE: tissue
DB_USERNAME: tissue
DB_PASSWORD: tissue
- image: circleci/postgres:10-alpine
environment:
POSTGRES_DB: tissue
POSTGRES_USER: tissue
POSTGRES_PASSWORD: tissue
commands:
initialize:
steps:
- checkout
- run: sudo apt update
- run: sudo apt install -y libpq-dev
- run: sudo docker-php-ext-install zip
- run: sudo docker-php-ext-install pdo_pgsql
restore_composer:
steps:
- restore_cache:
keys:
- v1-dependencies-{{ checksum "composer.json" }}
- v1-dependencies-
save_composer:
steps:
- save_cache:
key: v1-dependencies-{{ checksum "composer.json" }}
paths:
- ./vendor
restore_npm:
steps:
- restore_cache:
keys:
- v1-dependencies-{{ checksum "package.json" }}
- v1-dependencies-
save_npm:
steps:
- save_cache:
key: v1-dependencies-{{ checksum "package.json" }}
paths:
- ./node_modules
- ~/.yarn
jobs:
build:
executor: build
steps:
- initialize
- restore_composer
- run: composer install -n --prefer-dist
- save_composer
- restore_npm
- run: yarn install
- save_npm
- run: yarn run prod
- persist_to_workspace:
root: .
paths:
- public
test:
executor: build
steps:
- initialize
- restore_composer
- restore_npm
- attach_workspace:
at: .
- run: php artisan migrate
# Run linter
- run:
command: |
mkdir -p /tmp/php-cs-fixer
./vendor/bin/php-cs-fixer fix --dry-run --diff --format=junit > /tmp/php-cs-fixer/php-cs-fixer.xml
when: always
- store_test_results:
path: /tmp/php-cs-fixer
# Run stylelint
- run:
name: stylelint
command: yarn run stylelint
when: always
# Run unit test
- run:
command: |
mkdir -p /tmp/phpunit
./vendor/bin/phpunit --log-junit /tmp/phpunit/phpunit.xml --coverage-clover=/tmp/phpunit/coverage.xml
when: always
- store_test_results:
path: /tmp/phpunit
- store_artifacts:
path: /tmp/phpunit/coverage.xml
# Upload coverage
- run:
command: bash <(curl -s https://codecov.io/bash) -f /tmp/phpunit/coverage.xml
when: always
test_resolver:
executor: build
environment:
TEST_USE_HTTP_MOCK: false
steps:
- initialize
- restore_composer
- attach_workspace:
at: .
- run: php artisan migrate
# Run unit test
- run:
command: |
mkdir -p /tmp/phpunit
./vendor/bin/phpunit --testsuite MetadataResolver --log-junit /tmp/phpunit/phpunit.xml --coverage-clover=/tmp/phpunit/coverage.xml
when: always
- store_test_results:
path: /tmp/phpunit
- store_artifacts:
path: /tmp/phpunit/coverage.xml
workflows:
version: 2.1
test:
jobs:
- build
- test:
requires:
- build
scheduled_resolver_test:
triggers:
- schedule:
cron: "4 0 * * 1"
filters:
branches:
only:
- develop
jobs:
- build
- test_resolver:
requires:
- build

21
.editorconfig Normal file
View File

@@ -0,0 +1,21 @@
root = true
[*]
indent_style = space
indent_size = 4
charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[*.yml]
indent_size = 2
[*.json]
indent_size = 2
[composer.json]
indent_size = 4

View File

@@ -5,6 +5,9 @@ APP_DEBUG=true
APP_LOG_LEVEL=debug
APP_URL=http://localhost
# テストにモックを使用するか falseの場合は実際のHTML等を取得してテストする
TEST_USE_HTTP_MOCK=true
DB_CONNECTION=pgsql
DB_HOST=db
DB_PORT=5432

5
.gitignore vendored
View File

@@ -1,9 +1,14 @@
/node_modules
/public/css
/public/fonts
/public/js
/public/hot
/public/storage
/public/mix-manifest.json
/storage/*.key
/vendor
/.idea
/.vscode
/.vagrant
Homestead.json
Homestead.yaml

1
.stylelintignore Normal file
View File

@@ -0,0 +1 @@
/tests/fixture/*

View File

@@ -1,4 +1,6 @@
FROM php:7.1-apache
FROM node:10-jessie as node
FROM php:7.3-apache
ENV APACHE_DOCUMENT_ROOT /var/www/html/public
@@ -8,6 +10,7 @@ RUN apt-get update \
&& pecl install xdebug \
&& curl -sS https://getcomposer.org/installer | php \
&& mv composer.phar /usr/local/bin/composer \
&& composer global require hirak/prestissimo \
&& sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf \
&& sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf \
&& a2enmod rewrite
@@ -15,6 +18,16 @@ RUN apt-get update \
COPY dist/bin /usr/local/bin/
COPY dist/php.d /usr/local/etc/php/php.d/
COPY --from=node /usr/local/bin/node /usr/local/bin/
COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules
COPY --from=node /opt/yarn-* /opt/yarn
RUN ln -s /opt/yarn/bin/yarn /usr/local/bin/yarn \
&& ln -s ../lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \
&& ln -s ../lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx
ENTRYPOINT ["tissue-entrypoint.sh"]
CMD ["apache2-foreground"]

View File

@@ -8,11 +8,11 @@ a.k.a. shikorism.net
## 構成
- Laravel 5.5
- Bootstrap 4.2.1
- Bootstrap 4.3.1
## 実行環境
- PHP 7.1
- PHP 7.3
- PostgreSQL 9.6
## 開発環境の構築
@@ -33,10 +33,11 @@ docker-compose build
docker-compose up -d
```
4. Composer を使い必要なライブラリをインストールします。
4. Composer と yarn を使い必要なライブラリをインストールします。
```
docker-compose exec web composer install
docker-compose exec web yarn install
```
5. 暗号化キーの作成と、データベースのマイグレーションを行います。
@@ -49,10 +50,17 @@ docker-compose exec web php artisan migrate
6. ファイルに書き込めるように権限を設定します。
```
docker-compose exec web chown -R www-data /var/www/html
docker-compose exec web chown -R www-data /var/www/html/storage
```
7. 最後に `.env` を読み込み直すために起動し直します。
7. アセットをビルドします。
```
docker-compose exec web yarn dev
```
8. 最後に `.env` を読み込み直すために起動し直します。
```
docker-compose up -d
@@ -68,6 +76,25 @@ docker-compose -f docker-compose.yml -f docker-compose.debug.yml up -d
で起動することにより、DB のポート`5432`を開放してホストマシンから接続できるようになります。
## アセットのリアルタイムビルド
`yarn watch`を使うとソースファイルを監視して差分があると差分ビルドしてくれます。フロント開発時は活用しましょう。
```
docker-compose run --rm web yarn watch
```
もしファイル変更時に更新されない場合は`yarn watch-poll`を試してみてください。
現在Docker環境でのHMRはサポートしてません。Docker外ならおそらく動くでしょう。
その他詳しくはlaravel-mixのドキュメントなどを当たってください。
## phpunit によるテスト
変更をしたらPull Requestを投げる前にテストが通ることを確認してください。
テストは以下のコマンドで実行できます。
```
docker-compose exec web composer test
```
## 環境構築上の諸注意
- 初版時点では、DB サーバとして PostgreSQL を使うよう .env ファイルを設定するくらいです。

View File

@@ -0,0 +1,61 @@
<?php
namespace App\Console\Commands;
use App\User;
use Illuminate\Console\Command;
class DemoteUser extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'tissue:user:demote {username}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Demote admin to user';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$user = User::where('name', $this->argument('username'))->first();
if ($user === null) {
$this->error('No user with such username');
return 1;
}
if (!$user->is_admin) {
$this->info('@' . $user->name . ' is already an user.');
return 0;
}
$user->is_admin = false;
if ($user->save()) {
$this->info('@' . $user->name . ' is an user now.');
} else {
$this->error('Something happened.');
}
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace App\Console\Commands;
use App\User;
use Illuminate\Console\Command;
class PromoteUser extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'tissue:user:promote {username}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Promote user to admin';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$user = User::where('name', $this->argument('username'))->first();
if ($user === null) {
$this->error('No user with such username');
return 1;
}
if ($user->is_admin) {
$this->info('@' . $user->name . ' is already an administrator.');
return 0;
}
$user->is_admin = true;
if ($user->save()) {
$this->info('@' . $user->name . ' is an administrator now.');
} else {
$this->error('Something happened.');
}
}
}

View File

@@ -2,6 +2,8 @@
namespace App\Console;
use App\Console\Commands\DemoteUser;
use App\Console\Commands\PromoteUser;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@@ -35,6 +37,8 @@ class Kernel extends ConsoleKernel
*/
protected function commands()
{
$this->load(__DIR__.'/Commands');
require base_path('routes/console.php');
}
}

View File

@@ -2,16 +2,20 @@
namespace App;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Staudenmeir\EloquentEagerLimit\HasEagerLimit;
class Ejaculation extends Model
{
//
use HasEagerLimit;
protected $fillable = [
'user_id', 'ejaculated_date',
'note', 'geo_latitude', 'geo_longitude', 'link',
'is_private'
'is_private', 'is_too_sensitive'
];
protected $dates = [
@@ -34,4 +38,60 @@ class Ejaculation extends Model
return $v->name;
})->all());
}
public function likes()
{
return $this->hasMany(Like::class);
}
public function scopeWithLikes(Builder $query)
{
if (Auth::check()) {
// TODO - このスコープを使うことでlikesが常に直近10件で絞られるのは汚染されすぎ感がある。別名を付与できないか
// - (ejaculation_id, user_id) でユニークなわけですが、is_liked はサブクエリ発行させるのとLeft JoinしてNULLかどうかで結果を見るのどっちがいいんでしょうね
return $query
->with([
'likes' => function ($query) {
$query->latest()->take(10);
},
'likes.user' => function ($query) {
$query->where('is_protected', false)
->where('private_likes', false)
->orWhere('id', Auth::id());
}
])
->withCount([
'likes',
'likes as is_liked' => function ($query) {
$query->where('user_id', Auth::id());
}
]);
} else {
return $query
->with([
'likes' => function ($query) {
$query->latest()->take(10);
},
'likes.user' => function ($query) {
$query->where('is_protected', false)
->where('private_likes', false);
}
])
->withCount('likes')
->addSelect(DB::raw('0 as is_liked'));
}
}
/**
* このチェックインと同じ情報を流用してチェックインするためのURLを生成
* @return string
*/
public function makeCheckinURL(): string
{
return route('checkin', [
'link' => $this->link,
'tags' => $this->textTags(),
'is_too_sensitive' => $this->is_too_sensitive,
]);
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class DashboardController extends Controller
{
public function index()
{
return view('admin.dashboard');
}
}

View File

@@ -0,0 +1,75 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\AdminInfoStoreRequest;
use App\Information;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
class InfoController extends Controller
{
public function index()
{
$informations = Information::query()
->select('id', 'category', 'pinned', 'title', 'created_at')
->orderByDesc('pinned')
->orderByDesc('created_at')
->paginate(20);
return view('admin.info.index')->with([
'informations' => $informations,
'categories' => Information::CATEGORIES
]);
}
public function create()
{
return view('admin.info.create')->with([
'categories' => Information::CATEGORIES
]);
}
public function store(AdminInfoStoreRequest $request)
{
$inputs = $request->all();
if (!$request->has('pinned')) {
$inputs['pinned'] = false;
}
$info = Information::create($inputs);
return redirect()->route('admin.info.edit', ['info' => $info])->with('status', 'お知らせを更新しました。');
}
public function edit($id)
{
$information = Information::findOrFail($id);
return view('admin.info.edit')->with([
'info' => $information,
'categories' => Information::CATEGORIES
]);
}
public function update(AdminInfoStoreRequest $request, Information $info)
{
$inputs = $request->all();
if (!$request->has('pinned')) {
$inputs['pinned'] = false;
}
$info->fill($inputs)->save();
return redirect()->route('admin.info.edit', ['info' => $info])->with('status', 'お知らせを更新しました。');
}
public function destroy(Information $info)
{
$info->delete();
return redirect()->route('admin.info')->with('status', 'お知らせを削除しました。');
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace App\Http\Controllers\Api;
use App\Metadata;
use App\MetadataResolver\MetadataResolver;
use App\Tag;
use App\Utilities\Formatter;
use Illuminate\Http\Request;
class CardController
{
/**
* @var MetadataResolver
*/
private $resolver;
/**
* @var Formatter
*/
private $formatter;
public function __construct(MetadataResolver $resolver, Formatter $formatter)
{
$this->resolver = $resolver;
$this->formatter = $formatter;
}
public function show(Request $request)
{
$request->validate([
'url:required|url'
]);
$url = $this->formatter->normalizeUrl($request->input('url'));
$metadata = Metadata::find($url);
if ($metadata === null || ($metadata->expires_at !== null && $metadata->expires_at < now())) {
$resolved = $this->resolver->resolve($url);
$metadata = Metadata::updateOrCreate(['url' => $url], [
'title' => $resolved->title,
'description' => $resolved->description,
'image' => $resolved->image,
'expires_at' => $resolved->expires_at
]);
$tagIds = [];
foreach ($resolved->tags as $tagName) {
$tag = Tag::firstOrCreate(['name' => $tagName]);
$tagIds[] = $tag->id;
}
$metadata->tags()->sync($tagIds);
}
$metadata->load('tags');
$response = response($metadata);
if (!config('app.debug')) {
$response = $response->setCache(['public' => true, 'max_age' => 86400]);
}
return $response;
}
}

View File

@@ -0,0 +1,73 @@
<?php
namespace App\Http\Controllers\Api;
use App\Ejaculation;
use App\Http\Controllers\Controller;
use App\Like;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
class LikeController extends Controller
{
public function store(Request $request)
{
$request->validate([
'id' => 'required|integer|exists:ejaculations'
]);
$keys = [
'user_id' => Auth::id(),
'ejaculation_id' => $request->input('id')
];
$like = Like::query()->where($keys)->first();
if ($like) {
$data = [
'errors' => [
['message' => 'このチェックインはすでにいいね済です。']
],
'ejaculation' => $like->ejaculation
];
return response()->json($data, 409);
}
$like = Like::create($keys);
return [
'ejaculation' => $like->ejaculation
];
}
public function destroy($id)
{
Validator::make(compact('id'), [
'id' => 'required|integer'
])->validate();
$like = Like::query()->where([
'user_id' => Auth::id(),
'ejaculation_id' => $id
])->first();
if ($like === null) {
$ejaculation = Ejaculation::find($id);
$data = [
'errors' => [
['message' => 'このチェックインはいいねされていません。']
],
'ejaculation' => $ejaculation
];
return response()->json($data, 404);
}
$like->delete();
return [
'ejaculation' => $like->ejaculation
];
}
}

View File

@@ -21,7 +21,8 @@ class EjaculationController extends Controller
'link' => $request->input('link', ''),
'tags' => $request->input('tags', ''),
'note' => $request->input('note', ''),
'is_private' => $request->input('is_private', 0) == 1
'is_private' => $request->input('is_private', 0) == 1,
'is_too_sensitive' => $request->input('is_too_sensitive', 0) == 1
];
return view('ejaculation.checkin')->with('defaults', $defaults);
@@ -30,9 +31,6 @@ class EjaculationController extends Controller
public function store(Request $request)
{
$inputs = $request->all();
if ($request->has('note')) {
$inputs['note'] = str_replace(["\r\n", "\r"], "\n", $inputs['note']);
}
$validator = Validator::make($inputs, [
'date' => 'required|date_format:Y/m/d',
@@ -59,13 +57,18 @@ class EjaculationController extends Controller
'ejaculated_date' => Carbon::createFromFormat('Y/m/d H:i', $inputs['date'] . ' ' . $inputs['time']),
'note' => $inputs['note'] ?? '',
'link' => $inputs['link'] ?? '',
'is_private' => $request->has('is_private') ?? false
'is_private' => $request->has('is_private') ?? false,
'is_too_sensitive' => $request->has('is_too_sensitive') ?? false
]);
$tagIds = [];
if (!empty($inputs['tags'])) {
$tags = explode(' ', $inputs['tags']);
foreach ($tags as $tag) {
if ($tag === '') {
continue;
}
$tag = Tag::firstOrCreate(['name' => $tag]);
$tagIds[] = $tag->id;
}
@@ -81,7 +84,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つ前のチェックインからの経過時間を求める
@@ -105,6 +110,8 @@ class EjaculationController extends Controller
{
$ejaculation = Ejaculation::findOrFail($id);
$this->authorize('edit', $ejaculation);
return view('ejaculation.edit')->with(compact('ejaculation'));
}
@@ -112,10 +119,9 @@ class EjaculationController extends Controller
{
$ejaculation = Ejaculation::findOrFail($id);
$this->authorize('edit', $ejaculation);
$inputs = $request->all();
if ($request->has('note')) {
$inputs['note'] = str_replace(["\r\n", "\r"], "\n", $inputs['note']);
}
$validator = Validator::make($inputs, [
'date' => 'required|date_format:Y/m/d',
@@ -141,13 +147,18 @@ class EjaculationController extends Controller
'ejaculated_date' => Carbon::createFromFormat('Y/m/d H:i', $inputs['date'] . ' ' . $inputs['time']),
'note' => $inputs['note'] ?? '',
'link' => $inputs['link'] ?? '',
'is_private' => $request->has('is_private') ?? false
'is_private' => $request->has('is_private') ?? false,
'is_too_sensitive' => $request->has('is_too_sensitive') ?? false
])->save();
$tagIds = [];
if (!empty($inputs['tags'])) {
$tags = explode(' ', $inputs['tags']);
foreach ($tags as $tag) {
if ($tag === '') {
continue;
}
$tag = Tag::firstOrCreate(['name' => $tag]);
$tagIds[] = $tag->id;
}
@@ -164,6 +175,9 @@ class EjaculationController extends Controller
public function destroy($id)
{
$ejaculation = Ejaculation::findOrFail($id);
$this->authorize('edit', $ejaculation);
$user = User::findOrFail($ejaculation->user_id);
$ejaculation->tags()->detach();
$ejaculation->delete();

View File

@@ -69,6 +69,7 @@ SQL
->orderBy('ejaculations.ejaculated_date', 'desc')
->select('ejaculations.*')
->with('user', 'tags')
->withLikes()
->take(10)
->get();

View File

@@ -5,6 +5,7 @@ namespace App\Http\Controllers;
use App\Ejaculation;
use App\Tag;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class SearchController extends Controller
{
@@ -18,9 +19,16 @@ class SearchController extends Controller
->whereHas('tags', function ($query) use ($inputs) {
$query->where('name', 'like', "%{$inputs['q']}%");
})
->whereHas('user', function ($query) {
$query->where('is_protected', false);
if (Auth::check()) {
$query->orWhere('id', Auth::id());
}
})
->where('is_private', false)
->orderBy('ejaculated_date', 'desc')
->with(['user', 'tags'])
->withLikes()
->paginate(20)
->appends($inputs);

View File

@@ -5,6 +5,7 @@ namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
class SettingController extends Controller
{
@@ -18,10 +19,18 @@ class SettingController extends Controller
$inputs = $request->all();
$validator = Validator::make($inputs, [
'display_name' => 'required|string|max:20',
'email' => [
'required',
'string',
'email',
'max:255',
Rule::unique('users')->ignore(Auth::user()->email, 'email')
],
'bio' => 'nullable|string|max:160',
'url' => 'nullable|url|max:2000'
], [], [
'display_name' => '名前',
'email' => 'メールアドレス',
'bio' => '自己紹介',
'url' => 'URL'
]);
@@ -32,6 +41,7 @@ class SettingController extends Controller
$user = Auth::user();
$user->display_name = $inputs['display_name'];
$user->email = $inputs['email'];
$user->bio = $inputs['bio'] ?? '';
$user->url = $inputs['url'] ?? '';
$user->save();
@@ -46,11 +56,12 @@ class SettingController extends Controller
public function updatePrivacy(Request $request)
{
$inputs = $request->all(['is_protected', 'accept_analytics']);
$inputs = $request->all(['is_protected', 'accept_analytics', 'private_likes']);
$user = Auth::user();
$user->is_protected = $inputs['is_protected'] ?? false;
$user->accept_analytics = $inputs['accept_analytics'] ?? false;
$user->private_likes = $inputs['private_likes'] ?? false;
$user->save();
return redirect()->route('setting.privacy')->with('status', 'プライバシー設定を更新しました。');

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Http\Controllers;
use App\Tag;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
class TagController extends Controller
{
public function index()
{
$tags = Tag::select(DB::raw(
<<<'SQL'
tags.name,
count(*) AS "checkins_count"
SQL
))
->join('ejaculation_tag', 'tags.id', '=', 'ejaculation_tag.tag_id')
->join('ejaculations', 'ejaculations.id', '=', 'ejaculation_tag.ejaculation_id')
->join('users', 'users.id', '=', 'ejaculations.user_id')
->where('ejaculations.is_private', false)
->where(function ($query) {
$query->where('users.is_protected', false);
if (Auth::check()) {
$query->orWhere('users.id', Auth::id());
}
})
->groupBy('tags.name')
->orderByDesc('checkins_count')
->orderBy('tags.name')
->paginate(100);
return view('tag.index', compact('tags'));
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Http\Controllers;
use App\Ejaculation;
use Illuminate\Http\Request;
class TimelineController extends Controller
{
public function showPublic()
{
$ejaculations = Ejaculation::join('users', 'users.id', '=', 'ejaculations.user_id')
->where('users.is_protected', false)
->where('ejaculations.is_private', false)
->where('ejaculations.link', '<>', '')
->orderBy('ejaculations.ejaculated_date', 'desc')
->select('ejaculations.*')
->with('user', 'tags')
->withLikes()
->paginate(21);
return view('timeline.public')->with(compact('ejaculations'));
}
}

View File

@@ -11,7 +11,10 @@ use Illuminate\Support\Facades\DB;
class UserController extends Controller
{
//
public function redirectMypage()
{
return redirect()->route('user.profile', ['name' => auth()->user()->name]);
}
public function profile($name)
{
@@ -27,6 +30,7 @@ id,
ejaculated_date,
note,
is_private,
is_too_sensitive,
link,
to_char(lead(ejaculated_date, 1, NULL) OVER (ORDER BY ejaculated_date DESC), 'YYYY/MM/DD HH24:MI') AS before_date,
to_char(ejaculated_date - (lead(ejaculated_date, 1, NULL) OVER (ORDER BY ejaculated_date DESC)), 'FMDDD日 FMHH24時間 FMMI分') AS ejaculated_span
@@ -38,6 +42,7 @@ SQL
}
$ejaculations = $query->orderBy('ejaculated_date', 'desc')
->with('tags')
->withLikes()
->paginate(20);
// よく使っているタグ
@@ -64,6 +69,8 @@ SQL
abort(404);
}
$dateUntil = now()->addMonth()->startOfMonth();
$groupByDay = Ejaculation::select(DB::raw(
<<<'SQL'
to_char(ejaculated_date, 'YYYY/MM/DD') AS "date",
@@ -71,6 +78,7 @@ count(*) AS "count"
SQL
))
->where('user_id', $user->id)
->where('ejaculated_date', '<', $dateUntil)
->groupBy(DB::raw("to_char(ejaculated_date, 'YYYY/MM/DD')"))
->orderBy(DB::raw("to_char(ejaculated_date, 'YYYY/MM/DD')"))
->get();
@@ -82,6 +90,7 @@ count(*) AS "count"
SQL
))
->where('user_id', $user->id)
->where('ejaculated_date', '<', $dateUntil)
->groupBy(DB::raw("to_char(ejaculated_date, 'HH24')"))
->orderBy(DB::raw('1'))
->get();
@@ -101,13 +110,6 @@ SQL
}
}
// 月間グラフ用の配列初期化
$month = Carbon::now()->firstOfMonth()->subMonth(11); // 直近12ヶ月
for ($i = 0; $i < 12; $i++) {
$monthlySum[$month->format('Y/m')] = 0;
$month->addMonth();
}
foreach ($groupByDay as $data) {
$date = Carbon::createFromFormat('Y/m/d', $data->date);
$yearAndMonth = $date->format('Y/m');
@@ -115,9 +117,7 @@ SQL
$dailySum[$date->timestamp] = $data->count;
$yearlySum[$date->year] += $data->count;
$dowSum[$date->dayOfWeek] += $data->count;
if (isset($monthlySum[$yearAndMonth])) {
$monthlySum[$yearAndMonth] += $data->count;
}
$monthlySum[$yearAndMonth] = ($monthlySum[$yearAndMonth] ?? 0) + $data->count;
}
foreach ($groupByHour as $data) {
@@ -125,7 +125,17 @@ SQL
$hourlySum[$hour] += $data->count;
}
return view('user.stats')->with(compact('user', 'dailySum', 'monthlySum', 'yearlySum', 'dowSum', 'hourlySum'));
$graphData = [
'dailySum' => $dailySum,
'dowSum' => $dowSum,
'monthlySum' => $monthlySum,
'yearlyKey' => array_keys($yearlySum),
'yearlySum' => array_values($yearlySum),
'hourlyKey' => array_keys($hourlySum),
'hourlySum' => array_values($hourlySum),
];
return view('user.stats')->with(compact('user', 'graphData'));
}
public function okazu($name)
@@ -142,6 +152,7 @@ id,
ejaculated_date,
note,
is_private,
is_too_sensitive,
link,
to_char(lead(ejaculated_date, 1, NULL) OVER (ORDER BY ejaculated_date DESC), 'YYYY/MM/DD HH24:MI') AS before_date,
to_char(ejaculated_date - (lead(ejaculated_date, 1, NULL) OVER (ORDER BY ejaculated_date DESC)), 'FMDDD日 FMHH24時間 FMMI分') AS ejaculated_span
@@ -158,4 +169,23 @@ SQL
return view('user.profile')->with(compact('user', 'ejaculations'));
}
public function likes($name)
{
$user = User::where('name', $name)->first();
if (empty($user)) {
abort(404);
}
$likes = $user->likes()
->orderBy('created_at', 'desc')
->with('ejaculation.user', 'ejaculation.tags')
->whereHas('ejaculation', function ($query) {
$query->where('user_id', Auth::id())
->orWhere('is_private', false);
})
->paginate(20);
return view('user.likes')->with(compact('user', 'likes'));
}
}

View File

@@ -18,6 +18,7 @@ class Kernel extends HttpKernel
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\TrustProxies::class,
];
/**
@@ -34,9 +35,15 @@ class Kernel extends HttpKernel
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\NormalizeLineEnding::class,
],
// 現時点では内部APIしかないので、認証の手間を省くためにステートフルにしている。
'api' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
'throttle:60,1',
'bindings',
],

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Http\Middleware;
use Closure;
/**
* リクエスト内の改行コードを正規化する。
* @package App\Http\Middleware
*/
class NormalizeLineEnding
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$newInput = [];
foreach ($request->input() as $key => $value) {
$newInput[$key] = str_replace(["\r\n", "\r"], "\n", $value);
}
$request->replace($newInput);
return $next($request);
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Http\Middleware;
use Fideloper\Proxy\TrustProxies as Middleware;
use Illuminate\Http\Request;
class TrustProxies extends Middleware
{
/**
* The trusted proxies for this application.
*
* @var array
*/
protected $proxies = '**';
/**
* The current proxy header mappings.
*
* @var array
*/
protected $headers = [
Request::HEADER_FORWARDED => 'FORWARDED',
Request::HEADER_X_FORWARDED_FOR => 'X_FORWARDED_FOR',
Request::HEADER_X_FORWARDED_HOST => 'X_FORWARDED_HOST',
Request::HEADER_X_FORWARDED_PORT => 'X_FORWARDED_PORT',
Request::HEADER_X_FORWARDED_PROTO => 'X_FORWARDED_PROTO',
];
}

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Http\Requests;
use App\Information;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class AdminInfoStoreRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'category' => ['required', Rule::in(array_keys(Information::CATEGORIES))],
'pinned' => 'nullable|boolean',
'title' => 'required|string|max:255',
'content' => 'required|string|max:10000'
];
}
}

View File

@@ -35,9 +35,27 @@ class ProfileStatsComposer
}
// 概況欄のデータ取得
$average = DB::select(<<<'SQL'
SELECT
avg(span) AS average
FROM
(
SELECT
extract(epoch from ejaculated_date - lead(ejaculated_date, 1, NULL) OVER (ORDER BY ejaculated_date DESC)) AS span
FROM
ejaculations
WHERE
user_id = :user_id
ORDER BY
ejaculated_date DESC
LIMIT
30
) AS temp
SQL
, ['user_id' => $user->id]);
$summary = DB::select(<<<'SQL'
SELECT
avg(span) AS average,
max(span) AS longest,
min(span) AS shortest,
sum(span) AS total_times,
@@ -56,6 +74,6 @@ FROM
SQL
, ['user_id' => $user->id]);
$view->with(compact('latestEjaculation', 'currentSession', 'summary'));
$view->with(compact('latestEjaculation', 'currentSession', 'average', 'summary'));
}
}

View File

@@ -16,5 +16,9 @@ class Information extends Model
3 => ['label' => 'メンテナンス', 'class' => 'badge-warning']
];
protected $fillable = [
'category', 'pinned', 'title', 'content'
];
protected $dates = ['deleted_at'];
}

23
app/Like.php Normal file
View File

@@ -0,0 +1,23 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Staudenmeir\EloquentEagerLimit\HasEagerLimit;
class Like extends Model
{
use HasEagerLimit;
protected $fillable = ['user_id', 'ejaculation_id'];
public function user()
{
return $this->belongsTo(User::class);
}
public function ejaculation()
{
return $this->belongsTo(Ejaculation::class)->withLikes();
}
}

View File

@@ -5,6 +5,7 @@ namespace App\Listeners;
use App\Events\LinkDiscovered;
use App\Metadata;
use App\MetadataResolver\MetadataResolver;
use App\Tag;
use App\Utilities\Formatter;
use GuzzleHttp\Exception\TransferException;
use Illuminate\Contracts\Queue\ShouldQueue;
@@ -47,12 +48,19 @@ class LinkCollector
if ($metadata == null || ($metadata->expires_at !== null && $metadata->expires_at < now())) {
try {
$resolved = $this->metadataResolver->resolve($url);
Metadata::updateOrCreate(['url' => $url], [
$metadata = Metadata::updateOrCreate(['url' => $url], [
'title' => $resolved->title,
'description' => $resolved->description,
'image' => $resolved->image,
'expires_at' => $resolved->expires_at
]);
$tagIds = [];
foreach ($resolved->tags as $tagName) {
$tag = Tag::firstOrCreate(['name' => $tagName]);
$tagIds[] = $tag->id;
}
$metadata->tags()->sync($tagIds);
} catch (TransferException $e) {
// 何らかの通信エラーによってメタデータの取得に失敗した時、とりあえずエラーログにURLを残す
Log::error(self::class . ': メタデータの取得に失敗 URL=' . $url);

View File

@@ -11,7 +11,12 @@ class Metadata extends Model
protected $keyType = 'string';
protected $fillable = ['url', 'title', 'description', 'image', 'expires_at'];
protected $visible = ['url', 'title', 'description', 'image', 'expires_at'];
protected $visible = ['url', 'title', 'description', 'image', 'expires_at', 'tags'];
protected $dates = ['created_at', 'updated_at', 'expires_at'];
public function tags()
{
return $this->belongsToMany(Tag::class)->withTimestamps();
}
}

View File

@@ -2,9 +2,9 @@
namespace App\MetadataResolver;
use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Exception\TransferException;
use Illuminate\Support\Facades\Log;
use Psr\Http\Message\ResponseInterface;
class ActivityPubResolver implements Resolver, Parser
{
@@ -40,7 +40,7 @@ class ActivityPubResolver implements Resolver, Parser
$metadata = new Metadata();
$metadata->title = isset($object['attributedTo']) ? $this->getTitleFromActor($object['attributedTo']) : '';
$metadata->description .= isset($object['summary']) ? $object['summary'] . " | " : '';
$metadata->description .= isset($object['summary']) ? $object['summary'] . ' | ' : '';
$metadata->description .= isset($object['content']) ? $this->html2text($object['content']) : '';
$metadata->image = $object['attachment'][0]['url'] ?? '';
@@ -53,6 +53,7 @@ class ActivityPubResolver implements Resolver, Parser
$res = $this->activityClient->get($url);
if ($res->getStatusCode() !== 200) {
Log::info(self::class . ': Actorの取得に失敗 URL=' . $url);
return '';
}
@@ -65,16 +66,22 @@ class ActivityPubResolver implements Resolver, Parser
return $title;
} catch (TransferException $e) {
Log::info(self::class . ': Actorの取得に失敗 URL=' . $url);
return '';
}
}
private function html2text(string $html): string
{
if (empty($html)) {
return '';
}
$html = mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8');
$html = preg_replace('~<br\s*/?\s*>|</p>\s*<p[^>]*>~i', "\n", $html);
$dom = new \DOMDocument();
$dom->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
return $dom->textContent;
}
}

View File

@@ -25,19 +25,15 @@ class CienResolver extends MetadataResolver
public function resolve(string $url): Metadata
{
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
$metadata = $this->ogpResolver->parse((string) $res->getBody());
// 画像URLから有効期限の起点を拾う
parse_str(parse_url($metadata->image, PHP_URL_QUERY), $params);
if (empty($params['px-time'])) {
throw new \RuntimeException('Parameter "px-time" not found. Image=' . $metadata->image . ' Source=' . $url);
}
$metadata->expires_at = Carbon::createFromTimestamp($params['px-time'])->addHour(1);
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
// 画像URLから有効期限の起点を拾う
parse_str(parse_url($metadata->image, PHP_URL_QUERY), $params);
if (empty($params['px-time'])) {
throw new \RuntimeException('Parameter "px-time" not found. Image=' . $metadata->image . ' Source=' . $url);
}
$metadata->expires_at = Carbon::createFromTimestamp($params['px-time'])->addHour(1);
return $metadata;
}
}

View File

@@ -21,16 +21,109 @@ class DLsiteResolver implements Resolver
$this->ogpResolver = $ogpResolver;
}
/**
* HTMLからタグとして利用可能な情報を抽出する
* @param string $html ページ HTML
* @return string[] タグ
*/
public function extractTags(string $html): array
{
$dom = new \DOMDocument();
@$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
$xpath = new \DOMXPath($dom);
$genreNode = $xpath->query("//div[@class='main_genre'][1]");
if ($genreNode->length === 0) {
return [];
}
$tagsNode = $genreNode->item(0)->getElementsByTagName('a');
$tags = [];
for ($i = 0; $i <= $tagsNode->length - 1; $i++) {
$tags[] = $tagsNode->item($i)->textContent;
}
// 重複削除
$tags = array_values(array_unique($tags));
return $tags;
}
public function resolve(string $url): Metadata
{
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
$metadata->image = str_replace('img_sam.jpg', 'img_main.jpg', $metadata->image);
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
//アフィリエイトの場合は普通のURLに変換
// ID型
if (preg_match('~/dlaf/=(/.+/.+)?/link/~', $url)) {
preg_match('~www\.dlsite\.com/(?P<genre>.+)/dlaf/=(/.+/.+)?/link/work/aid/(?P<AffiliateId>.+)/id/(?P<titleId>..\d+)(\.html)?~', $url, $matches);
$url = "https://www.dlsite.com/{$matches['genre']}/work/=/product_id/{$matches['titleId']}.html";
}
// URL型
if (strpos($url, '/dlaf/=/aid/') !== false) {
preg_match('~www\.dlsite\.com/.+/dlaf/=/aid/.+/url/(?P<url>.+)~', $url, $matches);
$affiliateUrl = urldecode($matches['url']);
if (preg_match('~www\.dlsite\.com/.+/(work|announce)/=/product_id/..\d+(\.html)?~', $affiliateUrl, $matches)) {
$url = $affiliateUrl;
} else {
throw new \RuntimeException("アフィリエイト先のリンクがDLsiteのタイトルではありません: $affiliateUrl");
}
}
//スマホページの場合はPCページに正規化
if (strpos($url, '-touch') !== false) {
$url = str_replace('-touch', '', $url);
}
$res = $this->client->get($url);
$metadata = $this->ogpResolver->parse($res->getBody());
$dom = new \DOMDocument();
@$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8'));
$xpath = new \DOMXPath($dom);
// OGPタイトルから[]に囲まれているmakerを取得する
// 複数の作者がいる場合スペース区切りになるためexplodeしている
// スペースを含むmakerの場合名前の一部しか取れないが動作には問題ない
preg_match('~ \[([^\[\]]*)\] (予告作品 )?\| DLsite(がるまに)?$~', $metadata->title, $match);
$makers = explode(' ', $match[1]);
//フォローボタン(.btn_follow)はテキストを含んでしまうことがあるので要素を削除しておく
$followButtonNode = $xpath->query('//*[@class="btn_follow"]')->item(0);
$followButtonNode->parentNode->removeChild($followButtonNode);
// maker, makerHeadを探す
// makers
// #work_makerから「makerを含むテキスト」を持つ要素を持つtdを探す
// 作者名単体の場合もあるし、"作者A / 作者B"のようになることもある
$makersNode = $xpath->query('//*[@id="work_maker"]//*[contains(text(), "' . $makers[0] . '")]/ancestor::td')->item(0);
// nbspをspaceに置換
$makers = trim(str_replace("\xc2\xa0", ' ', $makersNode->textContent));
// makersHaed
// $makerNode(td)に対するthを探す
// "著者", "サークル名", "ブランド名"など
$makersHeadNode = $xpath->query('preceding-sibling::th', $makersNode)->item(0);
$makersHead = trim($makersHeadNode->textContent);
// 余分な文を消す
// OGPタイトルから作者名とサイト名を消す
$metadata->title = trim(preg_replace('~ \[[^\[\]]*\] (予告作品 )?\| DLsite(がるまに)?$~', '', $metadata->title));
// OGP説明文から定型文を消す
if (strpos($url, 'dlsite.com/eng/') || strpos($url, 'dlsite.com/ecchi-eng/')) {
$metadata->description = preg_replace('~DLsite.+ is a download shop for .+With a huge selection of products, we\'re sure you\'ll find whatever tickles your fancy\. DLsite is one of the greatest indie contents download shops in Japan\.$~', '', $metadata->description);
} else {
$metadata->description = preg_replace('~「DLsite.+」は.+のダウンロードショップ。お気に入りの作品をすぐダウンロードできてすぐ楽しめる毎日更新しているのであなたが探している作品にきっと出会えます。国内最大級の二次元総合ダウンロードショップ「DLsite」$~', '', $metadata->description);
}
$metadata->description = trim(strip_tags($metadata->description));
// 整形
$metadata->description = $makersHead . ': ' . $makers . PHP_EOL . $metadata->description;
$metadata->image = str_replace('img_sam.jpg', 'img_main.jpg', $metadata->image);
$metadata->tags = $this->extractTags($res->getBody());
return $metadata;
}
}

View File

@@ -23,35 +23,17 @@ class DeviantArtResolver implements Resolver
public function resolve(string $url): Metadata
{
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
$res = $this->client->get('https://backend.deviantart.com/oembed?url=' . $url);
$data = json_decode($res->getBody()->getContents(), true);
$metadata = new Metadata();
$dom = new \DOMDocument();
@$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8'));
$xpath = new \DOMXPath($dom);
$node = $xpath->query('//*[@id="pimp-preload"]/following-sibling::div//img')->item(0);
$srcset = $node->getAttribute('srcset');
$srcset_array = explode('w,', $srcset);
$src = end($srcset_array);
$src = preg_replace('~ \d+w$~', '', $src);
if (preg_match('~\.wixmp\.com$~', parse_url($src)['host'])) {
// アスペクト比を保ったまま、縦か横が最大700pxになるように変換する。
// Ref: https://support.wixmp.com/en/article/image-service-3835799
if (strpos($src, '/v1/fill/')) {
$src = preg_replace('~/v1/fill/w_\d+,h_\d+,q_\d+,strp~', '/v1/fit/w_700,h_700,q_70,strp', $src);
} else {
$src = $src . '/v1/fit/w_700,h_700,q_70,strp/image.jpg';
}
}
$metadata->image = $src;
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
$metadata->title = $data['title'] ?? '';
$metadata->description = 'By ' . $data['author_name'];
$metadata->image = $data['url'];
if (isset($data['tags'])) {
$metadata->tags = explode(', ', $data['tags']);
}
return $metadata;
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace App\MetadataResolver;
use GuzzleHttp\Client;
class FC2ContentsResolver implements Resolver
{
/**
* @var Client
*/
private $client;
/**
* @var OGPResolver
*/
private $ogpResolver;
public function __construct(Client $client, OGPResolver $ogpResolver)
{
$this->client = $client;
$this->ogpResolver = $ogpResolver;
}
public function resolve(string $url): Metadata
{
$res = $this->client->get($url);
$metadata = $this->ogpResolver->parse($res->getBody());
$dom = new \DOMDocument();
@$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8'));
$xpath = new \DOMXPath($dom);
$thumbnailNode = $xpath->query('//*[@class="main_thum_img"]/a')->item(0);
if ($thumbnailNode) {
$metadata->image = preg_replace('~^http:~', 'https:', $thumbnailNode->getAttribute('href'));
}
return $metadata;
}
}

View File

@@ -3,7 +3,6 @@
namespace App\MetadataResolver;
use GuzzleHttp\Client;
use Illuminate\Support\Facades\Log;
class FantiaResolver implements Resolver
{
@@ -11,45 +10,31 @@ class FantiaResolver implements Resolver
* @var Client
*/
private $client;
/**
* @var OGPResolver
*/
private $ogpResolver;
public function __construct(Client $client, OGPResolver $ogpResolver)
public function __construct(Client $client)
{
$this->client = $client;
$this->ogpResolver = $ogpResolver;
}
public function resolve(string $url): Metadata
{
preg_match("~\d+~", $url, $match);
$postId = $match[0];
preg_match("~posts/(\d+)~", $url, $match);
$postId = $match[1];
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
$res = $this->client->get("https://fantia.jp/api/v1/posts/{$postId}");
$data = json_decode(str_replace('\r\n', '\n', (string) $res->getBody()), true);
$post = $data['post'];
$dom = new \DOMDocument();
@$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8'));
$xpath = new \DOMXPath($dom);
$tags = array_map(function ($tag) {
return $tag['name'];
}, $post['tags']);
$node = $xpath->query("//meta[@property='twitter:image']")->item(0);
$ogpUrl = $node->getAttribute('content');
$metadata = new Metadata();
$metadata->title = $post['title'] ?? '';
$metadata->description = 'サークル: ' . $post['fanclub']['fanclub_name_with_creator_name'] . PHP_EOL . $post['comment'];
$metadata->image = str_replace('micro', 'main', $post['thumb_micro']) ?? '';
$metadata->tags = array_merge($tags, [$post['fanclub']['creator_name']]);
// 投稿に画像がない場合ogp.jpgでない場合のみ大きい画像に変換する
if ($ogpUrl != 'http://fantia.jp/images/ogp.jpg') {
preg_match("~https://fantia\.s3\.amazonaws\.com/uploads/post/file/{$postId}/ogp_(.*?)\.(jpg|png)~", $ogpUrl, $match);
$uuid = $match[1];
$extension = $match[2];
// 大きい画像に変換
$metadata->image = "https://c.fantia.jp/uploads/post/file/{$postId}/main_{$uuid}.{$extension}";
}
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
return $metadata;
}
}

View File

@@ -3,6 +3,7 @@
namespace App\MetadataResolver;
use GuzzleHttp\Client;
use Symfony\Component\DomCrawler\Crawler;
class FanzaResolver implements Resolver
{
@@ -21,16 +22,84 @@ class FanzaResolver implements Resolver
$this->ogpResolver = $ogpResolver;
}
/**
* arrayの各要素をtrim・スペースの_置換をした後、重複した値を削除してキーを詰め直す
*
* @param array $array
*
* @return array 処理されたarray
*/
public function array_finish(array $array): array
{
$array = array_map('trim', $array);
$array = array_map((function ($value) {
return str_replace(' ', '_', $value);
}), $array);
$array = array_unique($array);
$array = array_values($array);
return $array;
}
public function resolve(string $url): Metadata
{
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
$metadata->image = preg_replace("~(pr|ps)\.jpg$~", 'pl.jpg', $metadata->image);
$html = (string) $res->getBody();
$crawler = new Crawler($html);
// 動画
if (preg_match('~www\.dmm\.co\.jp/digital/(videoa|videoc|anime)/-/detail~', $url)) {
$metadata = new Metadata();
$metadata->title = trim($crawler->filter('#title')->text(''));
$metadata->description = trim($crawler->filter('.box-rank+table+div+div')->text(''));
$metadata->image = preg_replace("~(pr|ps)\.jpg$~", 'pl.jpg', $crawler->filter('meta[property="og:image"]')->attr('content'));
$metadata->tags = $this->array_finish($crawler->filter('.box-rank+table a:not([href="#review"])')->extract(['_text']));
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
// 同人
if (mb_strpos($url, 'www.dmm.co.jp/dc/doujin/-/detail/') !== false) {
$genre = $this->array_finish($crawler->filter('.m-productInformation a:not([href="#update-top"])')->extract(['_text']));
$genre = array_filter($genre, (function ($text) {
return !preg_match('~OFF対象$~', $text);
}));
$metadata = new Metadata();
$metadata->title = $crawler->filter('meta[property="og:title"]')->attr('content');
$metadata->description = trim($crawler->filter('.summary__txt')->text(''));
$metadata->image = $crawler->filter('meta[property="og:image"]')->attr('content');
$metadata->tags = array_merge($genre, [$crawler->filter('.circleName__txt')->text('')]);
return $metadata;
}
// 電子書籍
if (mb_strpos($url, 'book.dmm.co.jp/detail/') !== false) {
$metadata = new Metadata();
$metadata->title = trim($crawler->filter('#title')->text(''));
$metadata->description = trim($crawler->filter('.m-boxDetailProduct__info__story')->text(''));
$metadata->image = preg_replace("~(pr|ps)\.jpg$~", 'pl.jpg', $crawler->filter('meta[property="og:image"]')->attr('content'));
$metadata->tags = $this->array_finish($crawler->filter('.m-boxDetailProductInfoMainList__description__list__item, .m-boxDetailProductInfo__list__description__item a')->extract(['_text']));
return $metadata;
}
// PCゲーム
if (mb_strpos($url, 'dlsoft.dmm.co.jp/detail/') !== false) {
$metadata = new Metadata();
$metadata->title = trim($crawler->filter('#title')->text(''));
$metadata->description = trim($crawler->filter('.area-detail-read .text-overflow')->text(''));
$metadata->image = preg_replace("~(pr|ps)\.jpg$~", 'pl.jpg', $crawler->filter('meta[property="og:image"]')->attr('content'));
$metadata->tags = $this->array_finish($crawler->filter('.area-bskt table a:not([href="#review"])')->extract(['_text']));
return $metadata;
}
// 上で特に対応しなかったURL 画像の置換くらいはしておく
$metadata = $this->ogpResolver->parse($html);
$metadata->image = preg_replace("~(pr|ps)\.jpg$~", 'pl.jpg', $metadata->image);
return $metadata;
}
}

View File

@@ -3,6 +3,7 @@
namespace App\MetadataResolver;
use GuzzleHttp\Client;
use Symfony\Component\DomCrawler\Crawler;
class IwaraResolver implements Resolver
{
@@ -19,51 +20,41 @@ class IwaraResolver implements Resolver
public function resolve(string $url): Metadata
{
$res = $this->client->get($url);
$metadata = new Metadata();
$html = (string) $res->getBody();
$crawler = new Crawler($html);
if ($res->getStatusCode() === 200) {
$dom = new \DOMDocument();
@$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8'));
$xpath = new \DOMXPath($dom);
$infoElements = $crawler->filter('#video-player + div, .field-name-field-video-url + div, .field-name-field-images + div');
$title = $infoElements->filter('h1.title')->text();
$author = $infoElements->filter('.username')->text();
$description = $infoElements->filter('.field-type-text-with-summary')->text('');
$tags = $infoElements->filter('a[href^="/videos"], a[href^="/images"]')->extract('_text');
// 役に立たないタグを削除する
$tags = array_values(array_diff($tags, ['Uncategorized', 'Other']));
array_push($tags, $author);
$metadata = new Metadata();
$metadata->title = $title;
$metadata->description = '投稿者: ' . $author . PHP_EOL . $description;
$metadata->tags = $tags;
// find title
foreach ($xpath->query('//title') as $node) {
$content = $node->textContent;
if (!empty($content)) {
$metadata->title = $content;
break;
}
}
// find thumbnail
foreach ($xpath->query('//*[@id="video-player"]') as $node) {
$poster = $node->getAttribute('poster');
if (!empty($poster)) {
if (strpos($poster, '//') === 0) {
$poster = 'https:' . $poster;
}
$metadata->image = $poster;
break;
}
}
if (empty($metadata->image)) {
// YouTube embedded?
foreach ($xpath->query('//div[@class="embedded-video"]//iframe') as $node) {
$src = $node->getAttribute('src');
if (preg_match('~youtube\.com/embed/(\S+)\?~', $src, $matches) !== -1) {
$youtubeId = $matches[1];
$iwaraThumbUrl = 'https://i.iwara.tv/sites/default/files/styles/thumbnail/public/video_embed_field_thumbnails/youtube/' . $youtubeId . '.jpg';
$metadata->image = $iwaraThumbUrl;
break;
}
}
}
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
// iwara video
if ($crawler->filter('#video-player')->count()) {
$metadata->image = 'https:' . $crawler->filter('#video-player')->attr('poster');
}
// youtube
if ($crawler->filter('iframe[src^="//www.youtube.com"]')->count()) {
if (preg_match('~youtube\.com/embed/(\S+)\?~', $crawler->filter('iframe[src^="//www.youtube.com"]')->attr('src'), $matches) === 1) {
$youtubeId = $matches[1];
$metadata->image = 'https://img.youtube.com/vi/' . $youtubeId . '/maxresdefault.jpg';
}
}
// images
if ($crawler->filter('.field-name-field-images')->count()) {
$metadata->image = 'https:' . $crawler->filter('.field-name-field-images a')->first()->attr('href');
}
return $metadata;
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace App\MetadataResolver;
use GuzzleHttp\Client;
use Symfony\Component\DomCrawler\Crawler;
class Kb10uyShortStoryServerResolver implements Resolver
{
protected const EXCLUDED_TAGS = ['R-15', 'R-18'];
/**
* @var Client
*/
private $client;
public function __construct(Client $client)
{
$this->client = $client;
}
public function resolve(string $url): Metadata
{
$res = $this->client->get($url);
$html = (string) $res->getBody();
$crawler = new Crawler($html);
$infoElement = $crawler->filter('div.post-info');
$metadata = new Metadata();
$metadata->title = $infoElement->filter('h1')->text();
$metadata->description = trim($infoElement->filter('p.summary')->text());
$metadata->tags = array_values(array_diff($infoElement->filter('ul.tags > li.tag > a')->extract('_text'), self::EXCLUDED_TAGS));
return $metadata;
}
}

View File

@@ -24,18 +24,28 @@ class KomifloResolver implements Resolver
$id = $matches[1];
$res = $this->client->get('https://api.komiflo.com/content/id/' . $id);
if ($res->getStatusCode() === 200) {
$json = json_decode($res->getBody()->getContents(), true);
$metadata = new Metadata();
$json = json_decode($res->getBody()->getContents(), true);
$metadata = new Metadata();
$metadata->title = $json['content']['data']['title'] ?? '';
$metadata->description = ($json['content']['attributes']['artists']['children'][0]['data']['name'] ?? '?') .
' - ' .
($json['content']['parents'][0]['data']['title'] ?? '?');
$metadata->title = $json['content']['data']['title'] ?? '';
$metadata->description = ($json['content']['attributes']['artists']['children'][0]['data']['name'] ?? '?') .
' - ' . ($json['content']['parents'][0]['data']['title'] ?? '?');
$metadata->image = 'https://t.komiflo.com/564_mobile_large_3x/' . $json['content']['named_imgs']['cover']['filename'];
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
// 作者情報
if (!empty($json['content']['attributes']['artists']['children'])) {
foreach ($json['content']['attributes']['artists']['children'] as $artist) {
$metadata->tags[] = preg_replace('/\s/', '_', $artist['data']['name']);
}
}
// タグ
if (!empty($json['content']['attributes']['tags']['children'])) {
foreach ($json['content']['attributes']['tags']['children'] as $tag) {
$metadata->tags[] = preg_replace('/\s/', '_', $tag['data']['name']);
}
}
return $metadata;
}
}

View File

@@ -27,17 +27,42 @@ class MelonbooksResolver implements Resolver
$cookieJar = CookieJar::fromArray(['AUTH_ADULT' => '1'], 'www.melonbooks.co.jp');
$res = $this->client->get($url, ['cookies' => $cookieJar]);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
$metadata = $this->ogpResolver->parse($res->getBody());
// censoredフラグの除去
if (mb_strpos($metadata->image, '&c=1') !== false) {
$metadata->image = preg_replace('/&c=1/u', '', $metadata->image);
}
$dom = new \DOMDocument();
@$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8'));
$xpath = new \DOMXPath($dom);
$descriptionNodelist = $xpath->query('//div[@id="description"]//p');
$specialDescriptionNodelist = $xpath->query('//div[@id="special_description"]//p');
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
// censoredフラグの除去
if (mb_strpos($metadata->image, '&c=1') !== false) {
$metadata->image = preg_replace('/&c=1/u', '', $metadata->image);
}
// 抽出
preg_match('~^(.+)(.+))の通販・購入はメロンブックス$~', $metadata->title, $match);
$title = $match[1];
$maker = $match[2];
// 整形
$description = 'サークル: ' . $maker . "\n";
if ($specialDescriptionNodelist->length !== 0) {
$description .= trim(str_replace('<br>', "\n", $specialDescriptionNodelist->item(0)->nodeValue)) . "\n";
if ($specialDescriptionNodelist->length === 2) {
$description .= "\n";
$description .= trim(str_replace('<br>', "\n", $specialDescriptionNodelist->item(1)->nodeValue)) . "\n";
}
}
if ($descriptionNodelist->length !== 0) {
$description .= trim(str_replace('<br>', "\n", $descriptionNodelist->item(0)->nodeValue));
}
$metadata->title = $title;
$metadata->description = trim($description);
return $metadata;
}
}

View File

@@ -6,9 +6,21 @@ use Carbon\Carbon;
class Metadata
{
/** @var string タイトル */
public $title = '';
/** @var string 概要 */
public $description = '';
/** @var string サムネイルのURL */
public $image = '';
/** @var Carbon|null */
/** @var Carbon|null メタデータの有効期限 */
public $expires_at = null;
/**
* @var string[] タグ
* チェックインタグと同様に保存されるため、スペースや改行文字を含めてはいけません。
*/
public $tags = [];
}

View File

@@ -2,6 +2,7 @@
namespace App\MetadataResolver;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ServerException;
@@ -12,16 +13,26 @@ class MetadataResolver implements Resolver
'~nijie\.info/view(_popup)?\.php~' => NijieResolver::class,
'~komiflo\.com(/#!)?/comics/(\\d+)~' => KomifloResolver::class,
'~www\.melonbooks\.co\.jp/detail/detail\.php~' => MelonbooksResolver::class,
'~ec\.toranoana\.jp/tora_r/ec/item/.*~' => ToranoanaResolver::class,
'~iwara\.tv/videos/.*~' => IwaraResolver::class,
'~www\.dlsite\.com/.*/work/=/product_id/..\d+\.html~' => DLsiteResolver::class,
'~ec\.toranoana\.(jp|shop)/(tora|joshi)(_[rd]+)?/(ec|digi)/item/~' => ToranoanaResolver::class,
'~iwara\.tv/(videos|images)/.*~' => IwaraResolver::class,
'~www\.dlsite\.com/.*/(work|announce)/=/product_id/..\d+(\.html)?~' => DLsiteResolver::class,
'~www\.dlsite\.com/.*/dlaf/=(/.+/.+)?/link/work/aid/.+(/id)?/..\d+(\.html)?~' => DLsiteResolver::class,
'~www\.dlsite\.com/.*/dlaf/=/aid/.+/url/.+~' => DLsiteResolver::class,
'~dlsite\.jp/...tw/..\d+~' => DLsiteResolver::class,
'~www\.pixiv\.net/member_illust\.php\?illust_id=\d+~' => PixivResolver::class,
'~www\.pixiv\.net/artworks/\d+~' => PixivResolver::class,
'~www\.pixiv\.net/user/\d+/series/\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,
'~\.syosetu\.com/n\d+[a-z]{2,}~' => NarouResolver::class,
'~ci-en\.jp/creator/\d+/article/\d+~' => CienResolver::class,
'~www\.plurk\.com\/p\/.*~' => PlurkResolver::class,
'~(adult\.)?contents\.fc2\.com\/article_search\.php\?id=\d+~' => FC2ContentsResolver::class,
'~store\.steampowered\.com/app/\d+~' => SteamResolver::class,
'~www\.xtube\.com/video-watch/.*-\d+$~'=> XtubeResolver::class,
'~ss\.kb10uy\.org/posts/\d+$~' => Kb10uyShortStoryServerResolver::class,
];
public $mimeTypes = [
@@ -52,6 +63,7 @@ class MetadataResolver implements Resolver
if (isset($this->defaultResolver)) {
/** @var Resolver $resolver */
$resolver = app($this->defaultResolver);
return $resolver->resolve($url);
}
@@ -67,7 +79,7 @@ class MetadataResolver implements Resolver
// Acceptヘッダには */* を足さないことにする。
$acceptTypes = array_diff(array_keys($this->mimeTypes), ['*/*']);
$client = new \GuzzleHttp\Client();
$client = app(Client::class);
$res = $client->request('GET', $url, [
'headers' => [
'Accept' => implode(', ', $acceptTypes)
@@ -80,14 +92,14 @@ class MetadataResolver implements Resolver
if (isset($this->mimeTypes[$mimeType])) {
$class = $this->mimeTypes[$mimeType];
$parser = new $class();
$parser = app($class);
return $parser->parse($res->getBody());
}
if (isset($this->mimeTypes['*/*'])) {
$class = $this->mimeTypes['*/*'];
$parser = new $class();
$parser = app($class);
return $parser->parse($res->getBody());
}

View File

@@ -27,34 +27,30 @@ class NarouResolver implements Resolver
$cookieJar = CookieJar::fromArray(['over18' => 'yes'], '.syosetu.com');
$res = $this->client->get($url, ['cookies' => $cookieJar]);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
$metadata->description = '';
$metadata = $this->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);
$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 = [];
$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");
// 作者名
$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;
}
}

View File

@@ -3,6 +3,7 @@
namespace App\MetadataResolver;
use GuzzleHttp\Client;
use Symfony\Component\DomCrawler\Crawler;
class NicoSeigaResolver implements Resolver
{
@@ -24,16 +25,18 @@ class NicoSeigaResolver implements Resolver
public function resolve(string $url): Metadata
{
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
$html = (string)$res->getBody();
$metadata = $this->ogpResolver->parse($html);
$crawler = new Crawler($html);
// ページURLからサムネイルURLに変換
preg_match('~http://(?:(?:sp\\.)?seiga\\.nicovideo\\.jp/seiga(?:/#!)?|nico\\.ms)/im(\\d+)~', $url, $matches);
$metadata->image = "http://lohas.nicoseiga.jp/thumb/${matches[1]}l?";
// タグ
$excludeTags = ['R-15'];
$metadata->tags = array_values(array_diff($crawler->filter('.tag')->extract(['_text']), $excludeTags));
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
// ページURLからサムネイルURLに変換
preg_match('~https?://(?:(?:sp\\.)?seiga\\.nicovideo\\.jp/seiga(?:/#!)?|nico\\.ms)/im(\\d+)~', $url, $matches);
$metadata->image = "https://lohas.nicoseiga.jp/thumb/${matches[1]}l?";
return $metadata;
}
}

View File

@@ -3,6 +3,7 @@
namespace App\MetadataResolver;
use GuzzleHttp\Client;
use Symfony\Component\DomCrawler\Crawler;
class NijieResolver implements Resolver
{
@@ -30,27 +31,33 @@ class NijieResolver implements Resolver
$url = preg_replace('~view_popup\.php~', 'view.php', $url);
}
$client = $this->client;
$res = $client->get($url);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
$res = $this->client->get($url);
$html = (string) $res->getBody();
$metadata = $this->ogpResolver->parse($html);
$crawler = new Crawler($html);
$dom = new \DOMDocument();
@$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8'));
$xpath = new \DOMXPath($dom);
$dataNode = $xpath->query('//script[substring(@type, string-length(@type) - 3, 4) = "json"]');
foreach ($dataNode as $node) {
// 改行がそのまま入っていることがあるのでデコード前にエスケープが必要
$imageData = json_decode(preg_replace('/\r?\n/', '\n', $node->nodeValue), true);
if (isset($imageData['thumbnailUrl']) && !ends_with($imageData['thumbnailUrl'], '.gif') && !ends_with($imageData['thumbnailUrl'], '.mp4')) {
$metadata->image = preg_replace('~nijie\\.info/.*/nijie_picture/~', 'nijie.info/nijie_picture/', $imageData['thumbnailUrl']);
break;
}
}
$json = $crawler->filter('script[type="application/ld+json"]')->first()->text();
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
// 改行がそのまま入っていることがあるのでデコード前にエスケープが必要
$data = json_decode(preg_replace('/\r?\n/', '\n', $json), true);
// DomCrawler内でjson内の日本語がHTMLエンティティに変換されるので、全要素に対してhtml_entity_decode
array_walk_recursive($data, function (&$v) {
$v = html_entity_decode($v);
});
$metadata->title = $data['name'];
$metadata->description = '投稿者: ' . $data['author']['name'] . PHP_EOL . $data['description'];
if (
isset($data['thumbnailUrl']) &&
!ends_with($data['thumbnailUrl'], '.gif') &&
!ends_with($data['thumbnailUrl'], '.mp4')
) {
// サムネイルからメイン画像に
$metadata->image = str_replace('__rs_l160x160/', '', $data['thumbnailUrl']);
}
$metadata->tags = $crawler->filter('#view-tag span.tag_name')->extract('_text');
return $metadata;
}
}

View File

@@ -18,12 +18,7 @@ class OGPResolver implements Resolver, Parser
public function resolve(string $url): Metadata
{
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
return $this->parse($res->getBody());
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
return $this->parse($this->client->get($url)->getBody());
}
public function parse(string $html): Metadata

View File

@@ -5,4 +5,4 @@ namespace App\MetadataResolver;
interface Parser
{
public function parse(string $body): Metadata;
}
}

View File

@@ -25,18 +25,14 @@ class PatreonResolver implements Resolver
public function resolve(string $url): Metadata
{
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
$metadata = $this->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");
parse_str(parse_url($metadata->image, PHP_URL_QUERY), $query);
if (isset($query['token-time'])) {
$expires_at_unixtime = $query['token-time'];
$metadata->expires_at = Carbon::createFromTimestamp($expires_at_unixtime);
}
return $metadata;
}
}

View File

@@ -21,21 +21,6 @@ class PixivResolver implements Resolver
$this->ogpResolver = $ogpResolver;
}
/**
* サムネイル画像 URL から最大長辺 1200px の画像 URL に変換する
*
* @param string $thumbnailUrl サムネイル画像 URL
*
* @return string 1200px の画像 URL
*/
public function thumbnailToMasterUrl(string $thumbnailUrl): string
{
$temp = str_replace('/c/128x128', '', $thumbnailUrl);
$largeUrl = str_replace('square1200.jpg', 'master1200.jpg', $temp);
return $largeUrl;
}
/**
* 直リン可能な pixiv.cat のプロキシ URL に変換する
* HUGE THANKS TO PIXIV.CAT!
@@ -51,36 +36,50 @@ class PixivResolver implements Resolver
public function resolve(string $url): Metadata
{
parse_str(parse_url($url, PHP_URL_QUERY), $params);
$illustId = $params['illust_id'];
$page = 0;
// 漫画ページページ数はmanga_bigならあるかも
if ($params['mode'] === 'manga_big' || $params['mode'] === 'manga') {
$page = $params['page'] ?? 0;
// 未ログインでは漫画ページを開けないため、URL を作品ページに変換する
$url = preg_replace('~mode=manga(_big)?~', 'mode=medium', $url);
}
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
if (preg_match('~www\.pixiv\.net/user/\d+/series/\d+~', $url, $matches)) {
$res = $this->client->get($url);
$metadata = $this->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];
if ($page != 0) {
$illustThumbnailUrl = str_replace('_p0', '_p'.$page, $illustThumbnailUrl);
}
$illustUrl = $this->thumbnailToMasterUrl($illustThumbnailUrl);
$metadata->image = $this->proxize($illustUrl);
$metadata->image = $this->proxize($metadata->image);
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
$page = 0;
if (preg_match('~www\.pixiv\.net/artworks/(\d+)~', $url, $matches)) {
$illustId = $matches[1];
} else {
parse_str(parse_url($url, PHP_URL_QUERY), $params);
$illustId = $params['illust_id'];
// 漫画ページページ数はmanga_bigならあるかも
if ($params['mode'] === 'manga_big' || $params['mode'] === 'manga') {
$page = $params['page'] ?? 0;
}
}
$res = $this->client->get('https://www.pixiv.net/ajax/illust/' . $illustId);
$json = json_decode($res->getBody()->getContents(), true);
$metadata = new Metadata();
$metadata->title = $json['body']['illustTitle'] ?? '';
$metadata->description = '投稿者: ' . $json['body']['userName'] . PHP_EOL . strip_tags(str_replace('<br />', PHP_EOL, $json['body']['illustComment'] ?? ''));
$metadata->image = $this->proxize($json['body']['urls']['regular'] ?? '');
// ページ数の指定がある場合は画像URLをそのページにする
if ($page != 0) {
$metadata->image = str_replace('_p0', '_p' . $page, $metadata->image);
}
// タグ
if (!empty($json['body']['tags']['tags'])) {
foreach ($json['body']['tags']['tags'] as $tag) {
// 一部の固定キーワードは無視
if (array_search($tag['tag'], ['R-18', 'イラスト', 'pixiv', 'ピクシブ'], true) === false) {
$metadata->tags[] = preg_replace('/\s/', '_', $tag['tag']);
}
}
}
return $metadata;
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace App\MetadataResolver;
use GuzzleHttp\Client;
class PlurkResolver implements Resolver
{
/**
* @var Client
*/
private $client;
/**
* @var OGPResolver
*/
private $ogpResolver;
public function __construct(Client $client, OGPResolver $ogpResolver)
{
$this->client = $client;
$this->ogpResolver = $ogpResolver;
}
public function resolve(string $url): Metadata
{
$res = $this->client->get($url);
$metadata = $this->ogpResolver->parse($res->getBody());
$dom = new \DOMDocument();
@$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8'));
$xpath = new \DOMXPath($dom);
$imageNode = $xpath->query('//div[@class="text_holder"]/a[1]')->item(0);
if ($imageNode) {
$metadata->image = $imageNode->getAttribute('href');
}
return $metadata;
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace App\MetadataResolver;
use GuzzleHttp\Client;
class SteamResolver implements Resolver
{
/**
* @var Client
*/
private $client;
public function __construct(Client $client)
{
$this->client = $client;
}
public function resolve(string $url): Metadata
{
if (preg_match('~store\.steampowered\.com/app/(\d+)~', $url, $matches) !== 1) {
throw new \RuntimeException("Unmatched URL Pattern: $url");
}
$appid = $matches[1];
$res = $this->client->get('https://store.steampowered.com/api/appdetails/?l=japanese&appids=' . $appid);
$json = json_decode($res->getBody()->getContents(), true);
if ($json[$appid]['success'] === false) {
throw new \RuntimeException("API response [$appid][success] is false: $url");
}
$data = $json[$appid]['data'];
$metadata = new Metadata();
$metadata->title = $data['name'] ?? '';
$metadata->description = strip_tags(str_replace('<br />', PHP_EOL, html_entity_decode($data['short_description'] ?? '')));
$metadata->image = $data['header_image'] ?? '';
return $metadata;
}
}

View File

@@ -24,14 +24,17 @@ class ToranoanaResolver implements Resolver
public function resolve(string $url): Metadata
{
$cookieJar = CookieJar::fromArray(['adflg' => '0'], 'ec.toranoana.jp');
$res = $this->client->get($url);
$metadata = $this->ogpResolver->parse($res->getBody());
$res = $this->client->get($url, ['cookies' => $cookieJar]);
if ($res->getStatusCode() === 200) {
return $this->ogpResolver->parse($res->getBody());
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
$dom = new \DOMDocument();
@$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8'));
$xpath = new \DOMXPath($dom);
$imgNode = $xpath->query('//*[@id="preview"]//img')->item(0);
if ($imgNode !== null) {
$metadata->image = $imgNode->getAttribute('src');
}
return $metadata;
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\MetadataResolver;
use GuzzleHttp\Client;
use Symfony\Component\DomCrawler\Crawler;
class XtubeResolver implements Resolver
{
/**
* @var Client
*/
private $client;
/**
* @var OGPResolver
*/
private $ogpResolver;
public function __construct(Client $client, OGPResolver $ogpResolver)
{
$this->client = $client;
$this->ogpResolver = $ogpResolver;
}
public function resolve(string $url): Metadata
{
if (preg_match('~www\.xtube\.com/video-watch/.*-(\d+)$~', $url) !== 1) {
throw new \RuntimeException("Unmatched URL Pattern: $url");
}
$res = $this->client->get($url);
$html = (string) $res->getBody();
$metadata = $this->ogpResolver->parse($html);
$crawler = new Crawler($html);
$metadata->title = trim($crawler->filter('.underPlayerRateForm h1')->text(''));
$metadata->description = trim($crawler->filter('.fullDescription ')->text(''));
$metadata->image = str_replace('m=eSuQ8f', 'm=eaAaaEFb', $metadata->image);
$metadata->image = str_replace('240X180', 'original', $metadata->image);
$metadata->tags = array_map('trim', $crawler->filter('.tagsCategories a')->extract('_text'));
return $metadata;
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Policies;
use App\Ejaculation;
use App\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class EjaculationPolicy
{
use HandlesAuthorization;
/**
* Create a new policy instance.
*
* @return void
*/
public function __construct()
{
//
}
public function edit(User $user, Ejaculation $ejaculation): bool
{
return $user->id === $ejaculation->user_id;
}
}

View File

@@ -2,6 +2,8 @@
namespace App\Providers;
use App\Ejaculation;
use App\Policies\EjaculationPolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
@@ -14,6 +16,7 @@ class AuthServiceProvider extends ServiceProvider
*/
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
Ejaculation::class => EjaculationPolicy::class,
];
/**
@@ -25,6 +28,8 @@ class AuthServiceProvider extends ServiceProvider
{
$this->registerPolicies();
//
Gate::define('admin', function ($user) {
return $user->is_admin;
});
}
}

View File

@@ -11,6 +11,9 @@ class Tag extends Model
protected $fillable = [
'name'
];
protected $visible = [
'name'
];
public function ejaculations()
{

View File

@@ -20,6 +20,7 @@ class User extends Authenticatable
'is_protected', 'accept_analytics',
'display_name', 'description',
'twitter_id', 'twitter_name',
'private_likes',
];
/**
@@ -40,7 +41,7 @@ class User extends Authenticatable
{
$hash = md5(strtolower(trim($this->email)));
return '//www.gravatar.com/avatar/' . $hash . '?s=' . $size;
return '//www.gravatar.com/avatar/' . $hash . '?s=' . $size . '&d=retro';
}
/**
@@ -51,4 +52,9 @@ class User extends Authenticatable
{
return Auth::check() && $this->id === Auth::user()->id;
}
public function likes()
{
return $this->hasMany(Like::class);
}
}

View File

@@ -35,7 +35,7 @@ class Formatter
*/
public function linkify($text)
{
return $this->linkify->processUrls($text);
return $this->linkify->processUrls($text, ['attr' => ['target' => '_blank', 'rel' => 'noopener']]);
}
/**

View File

@@ -12,7 +12,10 @@
"guzzlehttp/guzzle": "^6.3",
"laravel/framework": "5.5.*",
"laravel/tinker": "~1.0",
"misd/linkify": "^1.1"
"misd/linkify": "^1.1",
"staudenmeir/eloquent-eager-limit": "^1.0",
"symfony/css-selector": "^4.3",
"symfony/dom-crawler": "^4.3"
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.1",
@@ -49,7 +52,10 @@
"@php artisan package:discover"
],
"fix": [
"php-cs-fixer fix"
"php-cs-fixer fix --config=.php_cs.dist"
],
"test": [
"phpunit"
]
},
"config": {

1334
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddBioAndUrlToUsers extends Migration
{

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddIsAdminToUsers extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->boolean('is_admin')->default(false);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('is_admin');
});
}
}

View File

@@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateLikesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('likes', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->index();
$table->integer('ejaculation_id')->index();
$table->timestamps();
$table->unique(['user_id', 'ejaculation_id']);
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('ejaculation_id')->references('id')->on('ejaculations')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('likes');
}
}

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddPrivateLikesToUsers extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->boolean('private_likes')->default(false);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('private_likes');
});
}
}

View File

@@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateMetadataTagTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('metadata_tag', function (Blueprint $table) {
$table->increments('id');
$table->text('metadata_url')->index();
$table->integer('tag_id')->index();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('metadata_tag');
}
}

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddIsTooSensitiveToEjaculations extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('ejaculations', function (Blueprint $table) {
$table->boolean('is_too_sensitive')->default(false);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('ejaculations', function (Blueprint $table) {
$table->dropColumn('is_too_sensitive');
});
}
}

View File

@@ -3,6 +3,15 @@ set -e
if [[ "$APP_DEBUG" == "true" ]]; then
export PHP_INI_SCAN_DIR=":/usr/local/etc/php/php.d"
php -r "if (gethostbyname('host.docker.internal') === 'host.docker.internal') exit(1);" &> /dev/null && :
if [[ $? -eq 0 ]]; then
# Docker for Windows/Mac
export PHP_XDEBUG_REMOTE_HOST='host.docker.internal'
else
# Docker for Linux
export PHP_XDEBUG_REMOTE_HOST=$(cat /etc/hosts | awk 'END{print $1}' | sed -r -e 's/[0-9]+$/1/g')
fi
fi
exec docker-php-entrypoint "$@"

View File

@@ -1,4 +1,4 @@
; Dockerでのデバッグ用設定
zend_extension=xdebug.so
xdebug.remote_enable=true
xdebug.remote_host=host.docker.internal
xdebug.remote_host=${PHP_XDEBUG_REMOTE_HOST}

View File

@@ -0,0 +1,10 @@
version: "3"
services:
mailcatcher:
image: schickling/mailcatcher
ports:
- 1080:1080
networks:
- backend

View File

@@ -7,15 +7,53 @@
"watch-poll": "npm run watch -- --watch-poll",
"hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
"prod": "npm run production",
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
"stylelint": "stylelint resources/assets/sass/**/*"
},
"devDependencies": {
"axios": "^0.16.2",
"bootstrap-sass": "^3.3.7",
"cross-env": "^5.0.1",
"jquery": "^3.1.1",
"laravel-mix": "^1.0",
"lodash": "^4.17.4",
"vue": "^2.1.10"
"@types/jquery": "^3.3.29",
"bootstrap": "^4.3.1",
"cal-heatmap": "^3.3.10",
"chart.js": "^2.7.1",
"cross-env": "^5.2.0",
"date-fns": "^1.30.1",
"grapheme-splitter": "^1.0.4",
"husky": "^1.3.1",
"jquery": "^3.2.1",
"js-cookie": "^2.2.0",
"laravel-mix": "^4.0.0",
"laravel-mix-bundle-analyzer": "^1.0.2",
"lint-staged": "^8.1.5",
"open-iconic": "^1.1.1",
"popper.js": "^1.14.7",
"resolve-url-loader": "^2.3.1",
"sass": "^1.17.0",
"sass-loader": "^7.1.0",
"stylelint": "^9.10.1",
"stylelint-config-recess-order": "^2.0.1",
"ts-loader": "^6.0.1",
"typescript": "^3.4.5",
"vue": "^2.6.10",
"vue-class-component": "^7.1.0",
"vue-property-decorator": "^8.1.1",
"vue-template-compiler": "^2.6.10"
},
"stylelint": {
"extends": "stylelint-config-recess-order"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{css,scss}": [
"stylelint --fix",
"git add"
],
"*.php": [
"composer fix",
"git add"
]
}
}

View File

@@ -16,6 +16,10 @@
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
<testsuite name="MetadataResolver">
<directory suffix="Test.php">./tests/Unit/MetadataResolver</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">

BIN
public/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

5
public/css/app.css vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,8 +0,0 @@
/*!
* Bootstrap Reboot v4.1.1 (https://getbootstrap.com/)
* Copyright 2011-2018 The Bootstrap Authors
* Copyright 2011-2018 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:transparent}@-ms-viewport{width:device-width}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent;-webkit-text-decoration-skip:objects}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg:not(:root){overflow:hidden}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}
/*# sourceMappingURL=bootstrap-reboot.min.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

BIN
public/dashboard.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 0 B

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

Binary file not shown.

View File

@@ -1,543 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<!--
2014-7-1: Created.
-->
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>
Created by FontForge 20120731 at Tue Jul 1 20:39:22 2014
By P.J. Onori
Created by P.J. Onori with FontForge 2.0 (http://fontforge.sf.net)
</metadata>
<defs>
<font id="open-iconic" horiz-adv-x="800" >
<font-face
font-family="Icons"
font-weight="400"
font-stretch="normal"
units-per-em="800"
panose-1="2 0 5 3 0 0 0 0 0 0"
ascent="800"
descent="0"
bbox="-0.5 -101 802 800.126"
underline-thickness="50"
underline-position="-100"
unicode-range="U+E000-E0DE"
/>
<missing-glyph />
<glyph glyph-name="" unicode="&#xe000;"
d="M300 700h500v-700h-500v100h400v500h-400v100zM400 500l200 -150l-200 -150v100h-400v100h400v100z" />
<glyph glyph-name="1" unicode="&#xe001;"
d="M300 700h500v-700h-500v100h400v500h-400v100zM200 500v-100h400v-100h-400v-100l-200 150z" />
<glyph glyph-name="2" unicode="&#xe002;"
d="M350 700c193 0 350 -157 350 -350v-50h100l-200 -200l-200 200h100v50c0 138 -112 250 -250 250s-250 -112 -250 -250c0 193 157 350 350 350z" />
<glyph glyph-name="3" unicode="&#xe003;"
d="M450 700c193 0 350 -157 350 -350c0 138 -112 250 -250 250s-250 -112 -250 -250v-50h100l-200 -200l-200 200h100v50c0 193 157 350 350 350z" />
<glyph glyph-name="4" unicode="&#xe004;"
d="M0 700h800v-100h-800v100zM100 500h600v-100h-600v100zM0 300h800v-100h-800v100zM100 100h600v-100h-600v100z" />
<glyph glyph-name="5" unicode="&#xe005;"
d="M0 700h800v-100h-800v100zM0 500h600v-100h-600v100zM0 300h800v-100h-800v100zM0 100h600v-100h-600v100z" />
<glyph glyph-name="6" unicode="&#xe006;"
d="M0 700h800v-100h-800v100zM200 500h600v-100h-600v100zM0 300h800v-100h-800v100zM200 100h600v-100h-600v100z" />
<glyph glyph-name="7" unicode="&#xe007;"
d="M400 700c75 0 146 -23 206 -59l-75 -225l-322 234c57 31 122 50 191 50zM125 588l191 -138l-310 -222c-4 24 -6 47 -6 72c0 114 49 215 125 288zM688 575c69 -72 112 -168 112 -275c0 -35 -8 -68 -16 -100h-218zM216 253l112 -347c-128 23 -232 109 -287 222zM372 100
h372c-64 -109 -177 -185 -310 -197z" />
<glyph glyph-name="8" unicode="&#xe008;" horiz-adv-x="600"
d="M200 800h100v-500h200l-247 -300l-253 300h200v500z" />
<glyph glyph-name="9" unicode="&#xe009;"
d="M400 800c221 0 400 -179 400 -400s-179 -400 -400 -400s-400 179 -400 400s179 400 400 400zM300 700v-300h-200l300 -300l300 300h-200v300h-200z" />
<glyph glyph-name="a" unicode="&#xe00a;"
d="M400 800c221 0 400 -179 400 -400s-179 -400 -400 -400s-400 179 -400 400s179 400 400 400zM400 700l-300 -300l300 -300v200h300v200h-300v200z" />
<glyph glyph-name="b" unicode="&#xe00b;"
d="M400 800c221 0 400 -179 400 -400s-179 -400 -400 -400s-400 179 -400 400s179 400 400 400zM400 700v-200h-300v-200h300v-200l300 300z" />
<glyph glyph-name="c" unicode="&#xe00c;"
d="M400 800c221 0 400 -179 400 -400s-179 -400 -400 -400s-400 179 -400 400s179 400 400 400zM400 700l-300 -300h200v-300h200v300h200z" />
<glyph glyph-name="d" unicode="&#xe00d;"
d="M300 600v-200h500v-100h-500v-200l-300 247z" />
<glyph glyph-name="e" unicode="&#xe00e;"
d="M500 600l300 -247l-300 -253v200h-500v100h500v200z" />
<glyph glyph-name="f" unicode="&#xe00f;" horiz-adv-x="600"
d="M200 800h200v-500h200l-297 -300l-303 300h200v500z" />
<glyph glyph-name="10" unicode="&#xe010;"
d="M300 700v-200h500v-200h-500v-200l-300 297z" />
<glyph glyph-name="11" unicode="&#xe011;"
d="M500 700l300 -297l-300 -303v200h-500v200h500v200z" />
<glyph glyph-name="12" unicode="&#xe012;" horiz-adv-x="600"
d="M297 800l303 -300h-200v-500h-200v500h-200z" />
<glyph glyph-name="13" unicode="&#xe013;" horiz-adv-x="600"
d="M247 800l253 -300h-200v-500h-100v500h-200z" />
<glyph glyph-name="14" unicode="&#xe014;"
d="M400 800h100v-800h-100v800zM200 700h100v-600h-100v600zM600 600h100v-400h-100v400zM0 500h100v-200h-100v200z" />
<glyph glyph-name="15" unicode="&#xe015;"
d="M116 600l72 -72c-54 -54 -88 -126 -88 -209s34 -159 88 -213l-72 -72c-72 72 -116 175 -116 285s44 209 116 281zM684 600c72 -72 116 -171 116 -281s-44 -213 -116 -285l-72 72c54 54 88 130 88 213s-34 155 -88 209zM259 460l69 -72c-18 -18 -28 -41 -28 -69
s10 -54 28 -72l-69 -72c-36 36 -59 89 -59 144s23 105 59 141zM541 459c36 -36 59 -85 59 -140s-23 -108 -59 -144l-69 72c18 18 28 44 28 72s-10 51 -28 69z" />
<glyph glyph-name="16" unicode="&#xe016;" horiz-adv-x="400"
d="M200 800c110 0 200 -90 200 -200s-90 -200 -200 -200s-200 90 -200 200s90 200 200 200zM100 319c31 -11 65 -19 100 -19s68 8 100 19v-319l-100 100l-100 -100v319z" />
<glyph glyph-name="17" unicode="&#xe017;"
d="M400 800c220 0 400 -180 400 -400s-180 -400 -400 -400s-400 180 -400 400s180 400 400 400zM400 700c-166 0 -300 -134 -300 -300c0 -66 21 -126 56 -175l419 419c-49 35 -109 56 -175 56zM644 575l-419 -419c49 -35 109 -56 175 -56c166 0 300 134 300 300
c0 66 -21 126 -56 175z" />
<glyph glyph-name="18" unicode="&#xe018;"
d="M0 700h100v-600h700v-100h-800v700zM500 700h200v-500h-200v500zM200 500h200v-300h-200v300z" />
<glyph glyph-name="19" unicode="&#xe019;"
d="M397 800c13 1 23 -4 34 -13c2 -2 214 -254 241 -287h128v-100h-100v-366c0 -18 -16 -34 -34 -34h-532c-18 0 -34 16 -34 34v366h-100v100h128l234 281c9 11 22 18 35 19zM400 672l-144 -172h288zM250 300c-28 0 -50 -22 -50 -50v-100c0 -28 22 -50 50 -50s50 22 50 50
v100c0 28 -22 50 -50 50zM550 300c-28 0 -50 -22 -50 -50v-100c0 -28 22 -50 50 -50s50 22 50 50v100c0 28 -22 50 -50 50z" />
<glyph glyph-name="1a" unicode="&#xe01a;"
d="M9 700h682c6 0 9 -4 9 -10v-190h100v-200h-100v-191c0 -6 -3 -9 -9 -9h-682c-6 0 -9 3 -9 9v582c0 6 3 9 9 9zM100 600v-400h500v400h-500z" />
<glyph glyph-name="1b" unicode="&#xe01b;"
d="M9 700h682c6 0 9 -4 9 -10v-190h100v-200h-100v-191c0 -6 -3 -9 -9 -9h-682c-6 0 -9 3 -9 9v582c0 6 3 9 9 9z" />
<glyph glyph-name="1c" unicode="&#xe01c;"
d="M92 650c0 23 19 50 45 50h3h5h5h500c28 0 50 -22 50 -50s-22 -50 -50 -50h-50v-141c9 -17 120 -231 166 -309c16 -26 34 -61 34 -106c0 -39 -15 -77 -41 -103h-3c-26 -25 -62 -41 -100 -41h-512c-39 0 -77 15 -103 41s-41 64 -41 103c0 46 18 80 34 106
c46 78 157 292 166 309v141h-50c-2 0 -6 -1 -8 -1c-28 0 -50 23 -50 51zM500 600h-200v-162l-6 -10s-63 -123 -119 -228h450c-56 105 -119 228 -119 228l-6 10v162z" />
<glyph glyph-name="1d" unicode="&#xe01d;"
d="M400 800c110 0 200 -90 200 -200c0 -104 52 -198 134 -266c41 -34 66 -82 66 -134h-800c0 52 25 100 66 134c82 68 134 162 134 266c0 110 90 200 200 200zM300 100h200c0 -55 -45 -100 -100 -100s-100 45 -100 100z" />
<glyph glyph-name="1e" unicode="&#xe01e;" horiz-adv-x="600"
d="M150 800h50l350 -250l-225 -147l225 -153l-350 -250h-50v250l-75 -75l-75 75l150 150l-150 150l75 75l75 -75v250zM250 650v-200l150 100zM250 350v-200l150 100z" />
<glyph glyph-name="1f" unicode="&#xe01f;"
d="M0 800h500c110 0 200 -90 200 -200c0 -47 -17 -91 -44 -125c85 -40 144 -125 144 -225c0 -138 -112 -250 -250 -250h-550v100c55 0 100 45 100 100v400c0 55 -45 100 -100 100v100zM300 700v-200h100c55 0 100 45 100 100s-45 100 -100 100h-100zM300 400v-300h150
c83 0 150 67 150 150s-67 150 -150 150h-150z" />
<glyph glyph-name="20" unicode="&#xe020;" horiz-adv-x="600"
d="M300 800v-300h200l-300 -500v300h-200z" />
<glyph glyph-name="21" unicode="&#xe021;"
d="M100 800h300v-300l100 100l100 -100v300h50c28 0 50 -22 50 -50v-550h-550c-28 0 -50 -22 -50 -50s22 -50 50 -50h550v-100h-550c-83 0 -150 67 -150 150v550l3 19c8 39 39 70 78 78z" />
<glyph glyph-name="22" unicode="&#xe022;" horiz-adv-x="400"
d="M0 800h400v-800l-200 200l-200 -200v800z" />
<glyph glyph-name="23" unicode="&#xe023;"
d="M0 800h800v-100h-800v100zM0 600h300v-103h203v103h297v-591c0 -6 -3 -9 -9 -9h-782c-6 0 -9 3 -9 9v591z" />
<glyph glyph-name="24" unicode="&#xe024;"
d="M300 800h200c55 0 100 -45 100 -100v-100h191c6 0 9 -3 9 -9v-241c0 -28 -22 -50 -50 -50h-700c-28 0 -50 22 -50 50v241c0 6 3 9 9 9h191v100c0 55 45 100 100 100zM300 700v-100h200v100h-200zM0 209c16 -6 32 -9 50 -9h700c18 0 34 3 50 9v-200c0 -6 -3 -9 -9 -9h-782
c-6 0 -9 3 -9 9v200z" />
<glyph glyph-name="25" unicode="&#xe025;" horiz-adv-x="600"
d="M300 800c58 0 110 -16 147 -53s53 -89 53 -147h-100c0 39 -11 61 -25 75s-36 25 -75 25c-35 0 -55 -10 -72 -31s-28 -55 -28 -94c0 -51 20 -107 28 -175h172v-100h-178c-14 -60 -49 -127 -113 -200h491v-100h-600v122l16 12c69 69 95 121 106 166h-122v100h125
c-8 50 -25 106 -25 175c0 58 16 114 50 156c34 43 88 69 150 69z" />
<glyph glyph-name="26" unicode="&#xe026;"
d="M34 700h4h3h4h5h700c28 0 50 -22 50 -50v-700c0 -28 -22 -50 -50 -50h-700c-28 0 -50 22 -50 50v700v2c0 20 15 42 34 48zM150 600c-28 0 -50 -22 -50 -50s22 -50 50 -50s50 22 50 50s-22 50 -50 50zM350 600c-28 0 -50 -22 -50 -50s22 -50 50 -50h300c28 0 50 22 50 50
s-22 50 -50 50h-300zM100 400v-400h600v400h-600z" />
<glyph glyph-name="27" unicode="&#xe027;"
d="M744 797l6 -3l44 -44c4 -4 3 -8 0 -12l-266 -375l-15 -13l-25 -12c-23 72 -78 127 -150 150l12 25l13 15l375 266zM266 400c74 0 134 -60 134 -134c0 -147 -119 -266 -266 -266c-48 0 -95 12 -134 34c80 46 134 133 134 232c0 74 58 134 132 134z" />
<glyph glyph-name="28" unicode="&#xe028;"
d="M9 451c0 23 19 50 46 50c8 0 19 -3 26 -7l131 -66l29 22c-79 81 -1 250 118 250s197 -167 119 -250l28 -22l131 66c6 4 12 7 21 7c28 0 50 -22 50 -50c0 -17 -12 -37 -27 -45l-115 -56c9 -16 19 -33 25 -50h68c28 0 50 -22 50 -50s-22 -50 -50 -50h-50
c0 -23 -2 -45 -6 -66l78 -40c21 -5 37 -28 37 -49c0 -28 -22 -50 -50 -50c-10 0 -23 5 -31 11l-65 35c-24 -46 -62 -86 -103 -110c-35 19 -60 45 -60 72v135v4v5v6v5v5v87c0 28 -22 50 -50 50c-24 0 -45 -17 -50 -40c1 -3 1 -8 1 -11s0 -8 -1 -11v-82v-4v-5v-144
c0 -28 -24 -53 -59 -72c-41 25 -79 64 -103 110l-66 -35c-8 -6 -21 -11 -31 -11c-28 0 -50 22 -50 50c0 21 16 44 37 49l78 40c-4 21 -6 43 -6 66h-50h-5c-28 0 -50 22 -50 50c0 26 22 50 50 50h5h69c6 17 16 34 25 50l-116 56c-16 7 -28 27 -28 45z" />
<glyph glyph-name="29" unicode="&#xe029;"
d="M600 700h91c6 0 9 -3 9 -9v-582c0 -6 -3 -9 -9 -9h-91v600zM210 503l290 147v-500l-250 125v-3c-15 0 -25 -8 -28 -22l75 -178c11 -25 0 -58 -25 -69s-58 0 -69 25l-103 272h-91c-6 0 -9 3 -9 9v182c0 6 3 9 9 9h182z" />
<glyph glyph-name="2a" unicode="&#xe02a;"
d="M9 800h682c6 0 9 -3 9 -9v-782c0 -6 -3 -9 -9 -9h-682c-6 0 -9 3 -9 9v782c0 6 3 9 9 9zM100 700v-200h500v200h-500zM100 400v-100h100v100h-100zM300 400v-100h100v100h-100zM500 400v-300h100v300h-100zM100 200v-100h100v100h-100zM300 200v-100h100v100h-100z" />
<glyph glyph-name="2b" unicode="&#xe02b;"
d="M0 800h700v-200h-700v200zM0 500h700v-491c0 -6 -3 -9 -9 -9h-682c-6 0 -9 3 -9 9v491zM100 400v-100h100v100h-100zM300 400v-100h100v100h-100zM500 400v-100h100v100h-100zM100 200v-100h100v100h-100zM300 200v-100h100v100h-100z" />
<glyph glyph-name="2c" unicode="&#xe02c;"
d="M409 800h182c6 0 10 -4 12 -9l94 -182c2 -5 6 -9 12 -9h82c6 0 9 -3 9 -9v-582c0 -6 -3 -9 -9 -9h-782c-6 0 -9 3 -9 9v441c0 83 67 150 150 150h141c6 0 10 4 12 9l94 182c2 5 6 9 12 9zM150 500c-28 0 -50 -22 -50 -50s22 -50 50 -50s50 22 50 50s-22 50 -50 50z
M500 500c-110 0 -200 -90 -200 -200s90 -200 200 -200s200 90 200 200s-90 200 -200 200zM500 400c55 0 100 -45 100 -100s-45 -100 -100 -100s-100 45 -100 100s45 100 100 100z" />
<glyph glyph-name="2d" unicode="&#xe02d;"
d="M0 600h800l-400 -400z" />
<glyph glyph-name="2e" unicode="&#xe02e;" horiz-adv-x="400"
d="M400 800v-800l-400 400z" />
<glyph glyph-name="2f" unicode="&#xe02f;" horiz-adv-x="400"
d="M0 800l400 -400l-400 -400v800z" />
<glyph glyph-name="30" unicode="&#xe030;"
d="M400 600l400 -400h-800z" />
<glyph glyph-name="31" unicode="&#xe031;"
d="M0 550c0 23 20 50 46 50h3h5h4h200c17 0 37 -13 44 -28l38 -72h444c14 0 19 -12 15 -25l-81 -250c-4 -13 -21 -25 -35 -25h-350c-14 0 -30 12 -34 25c-27 83 -54 167 -81 250l-10 25h-150c-2 0 -5 -1 -7 -1c-28 0 -51 23 -51 51zM358 100c28 0 50 -22 50 -50
s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM658 100c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50z" />
<glyph glyph-name="32" unicode="&#xe032;"
d="M0 700h500v-100h-300v-300h-100l-100 -100v500zM300 500h500v-500l-100 100h-400v400z" />
<glyph glyph-name="33" unicode="&#xe033;"
d="M641 700l143 -141l-493 -493c-71 76 -146 148 -219 222l-72 71l141 141c50 -51 101 -101 153 -150c116 117 234 231 347 350z" />
<glyph glyph-name="34" unicode="&#xe034;"
d="M150 600l250 -250l250 250l150 -150l-400 -400l-400 400z" />
<glyph glyph-name="35" unicode="&#xe035;" horiz-adv-x="600"
d="M400 800l150 -150l-250 -250l250 -250l-150 -150l-400 400z" />
<glyph glyph-name="36" unicode="&#xe036;" horiz-adv-x="600"
d="M150 800l400 -400l-400 -400l-150 150l250 250l-250 250z" />
<glyph glyph-name="37" unicode="&#xe037;"
d="M400 600l400 -400l-150 -150l-250 250l-250 -250l-150 150z" />
<glyph glyph-name="38" unicode="&#xe038;"
d="M400 800c221 0 400 -179 400 -400s-179 -400 -400 -400s-400 179 -400 400s179 400 400 400zM600 622l-250 -250l-100 100l-72 -72l172 -172l322 322z" />
<glyph glyph-name="39" unicode="&#xe039;"
d="M400 800c221 0 400 -179 400 -400s-179 -400 -400 -400s-400 179 -400 400s179 400 400 400zM250 622l-72 -72l150 -150l-150 -150l72 -72l150 150l150 -150l72 72l-150 150l150 150l-72 72l-150 -150z" />
<glyph glyph-name="3a" unicode="&#xe03a;"
d="M350 800c28 0 50 -22 50 -50v-50h75c14 0 25 -11 25 -25v-75h-300v75c0 14 11 25 25 25h75v50c0 28 22 50 50 50zM25 700h75v-200h500v200h75c14 0 25 -11 25 -25v-650c0 -14 -11 -25 -25 -25h-650c-14 0 -25 11 -25 25v650c0 14 11 25 25 25z" />
<glyph glyph-name="3b" unicode="&#xe03b;"
d="M400 800c220 0 400 -180 400 -400s-180 -400 -400 -400s-400 180 -400 400s180 400 400 400zM400 700c-166 0 -300 -134 -300 -300s134 -300 300 -300s300 134 300 300s-134 300 -300 300zM350 600h100v-181c23 -24 47 -47 72 -69l-72 -72c-27 30 -55 59 -84 88l-16 12
v222z" />
<glyph glyph-name="3c" unicode="&#xe03c;"
d="M450 800c138 0 250 -112 250 -250v-50c58 -21 100 -85 100 -150c0 -18 -3 -34 -9 -50h-191v50c0 83 -67 150 -150 150s-150 -67 -150 -150v-50h-272c-17 30 -28 63 -28 100c0 110 90 200 200 200c23 114 129 200 250 200zM434 400h3h4c3 0 6 1 9 1c28 0 50 -22 50 -50v-1
v-150h150l-200 -200l-200 200h150v150v2c0 20 15 42 34 48z" />
<glyph glyph-name="3d" unicode="&#xe03d;"
d="M450 800c138 0 250 -112 250 -250v-50c58 -21 100 -85 100 -150c0 -18 -3 -34 -9 -50h-141l-200 200l-200 -200h-222c-17 30 -28 63 -28 100c0 110 90 200 200 200c23 114 129 200 250 200zM450 350l250 -250h-200v-50c0 -28 -22 -50 -50 -50s-50 22 -50 50v50h-200z" />
<glyph glyph-name="3e" unicode="&#xe03e;"
d="M450 700c138 0 250 -112 250 -250v-50c58 -21 100 -85 100 -150c0 -83 -67 -150 -150 -150h-450c-110 0 -200 90 -200 200s90 200 200 200c23 114 129 200 250 200z" />
<glyph glyph-name="3f" unicode="&#xe03f;"
d="M250 800c82 0 154 -40 200 -100c-143 0 -270 -85 -325 -209c-36 -10 -70 -25 -100 -47c-16 33 -25 67 -25 106c0 138 112 250 250 250zM450 600c138 0 250 -112 250 -250v-50c58 -21 100 -85 100 -150c0 -83 -67 -150 -150 -150h-450c-110 0 -200 90 -200 200
s90 200 200 200c23 114 129 200 250 200z" />
<glyph glyph-name="40" unicode="&#xe040;"
d="M500 700h100l-300 -600h-100zM100 600h100l-100 -200l100 -200h-100l-100 200zM600 600h100l100 -200l-100 -200h-100l100 200z" />
<glyph glyph-name="41" unicode="&#xe041;"
d="M350 800h100l50 -119l28 -12l119 50l72 -72l-50 -119l12 -28l119 -50v-100l-119 -50l-12 -28l50 -119l-72 -72l-119 50l-28 -12l-50 -119h-100l-50 119l-28 12l-119 -50l-72 72l50 119l-12 28l-119 50v100l119 50l12 28l-50 119l72 72l119 -50l28 12zM400 550
c-83 0 -150 -67 -150 -150s67 -150 150 -150s150 67 150 150s-67 150 -150 150z" />
<glyph glyph-name="42" unicode="&#xe042;"
d="M0 800h800v-200h-800v200zM200 500h400l-200 -200zM0 100h800v-100h-800v100z" />
<glyph glyph-name="43" unicode="&#xe043;"
d="M0 800h100v-800h-100v800zM600 800h200v-800h-200v800zM500 600v-400l-200 200z" />
<glyph glyph-name="44" unicode="&#xe044;"
d="M0 800h200v-800h-200v800zM700 800h100v-800h-100v800zM300 600l200 -200l-200 -200v400z" />
<glyph glyph-name="45" unicode="&#xe045;"
d="M0 800h800v-100h-800v100zM400 500l200 -200h-400zM0 200h800v-200h-800v200z" />
<glyph glyph-name="46" unicode="&#xe046;"
d="M150 700c83 0 150 -67 150 -150v-50h100v50c0 83 67 150 150 150s150 -67 150 -150s-67 -150 -150 -150h-50v-100h50c83 0 150 -67 150 -150s-67 -150 -150 -150s-150 67 -150 150v50h-100v-50c0 -83 -67 -150 -150 -150s-150 67 -150 150s67 150 150 150h50v100h-50
c-83 0 -150 67 -150 150s67 150 150 150zM150 600c-28 0 -50 -22 -50 -50s22 -50 50 -50h50v50c0 28 -22 50 -50 50zM550 600c-28 0 -50 -22 -50 -50v-50h50c28 0 50 22 50 50s-22 50 -50 50zM300 400v-100h100v100h-100zM150 200c-28 0 -50 -22 -50 -50s22 -50 50 -50
s50 22 50 50v50h-50zM500 200v-50c0 -28 22 -50 50 -50s50 22 50 50s-22 50 -50 50h-50z" />
<glyph glyph-name="47" unicode="&#xe047;"
d="M0 791c0 5 4 9 9 9h782c6 0 9 -4 9 -10v-790l-200 200h-591c-6 0 -9 3 -9 9v582z" />
<glyph glyph-name="48" unicode="&#xe048;"
d="M400 800c220 0 400 -180 400 -400s-180 -400 -400 -400s-400 180 -400 400s180 400 400 400zM400 700c-166 0 -300 -134 -300 -300s134 -300 300 -300s300 134 300 300s-134 300 -300 300zM600 600l-100 -300l-300 -100l100 300zM400 450c-28 0 -50 -22 -50 -50
s22 -50 50 -50s50 22 50 50s-22 50 -50 50z" />
<glyph glyph-name="49" unicode="&#xe049;"
d="M400 800c220 0 400 -180 400 -400s-180 -400 -400 -400s-400 180 -400 400s180 400 400 400zM400 700v-600c166 0 300 134 300 300s-134 300 -300 300z" />
<glyph glyph-name="4a" unicode="&#xe04a;"
d="M0 800h800v-100h-800v100zM0 600h500v-100h-500v100zM0 300h800v-100h-800v100zM0 100h600v-100h-600v100zM750 100c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50z" />
<glyph glyph-name="4b" unicode="&#xe04b;"
d="M25 700h750c14 0 25 -11 25 -25v-75h-800v75c0 14 11 25 25 25zM0 500h800v-375c0 -14 -11 -25 -25 -25h-750c-14 0 -25 11 -25 25v375zM100 300v-100h100v100h-100zM300 300v-100h100v100h-100z" />
<glyph glyph-name="4c" unicode="&#xe04c;"
d="M100 800h100v-100h450l100 100l50 -50l-100 -100v-450h100v-100h-100v-100h-100v100h-500v500h-100v100h100v100zM200 600v-350l350 350h-350zM600 550l-350 -350h350v350z" />
<glyph glyph-name="4d" unicode="&#xe04d;"
d="M400 800c220 0 400 -180 400 -400s-180 -400 -400 -400s-400 180 -400 400s180 400 400 400zM400 700c-166 0 -300 -134 -300 -300s134 -300 300 -300s300 134 300 300s-134 300 -300 300zM400 600c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50z
M200 452c0 20 15 42 34 48h3h3h8c12 0 28 -7 36 -16l91 -90l25 6c55 0 100 -45 100 -100s-45 -100 -100 -100s-100 45 -100 100l6 25l-90 91c-9 8 -16 24 -16 36zM550 500c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50z" />
<glyph glyph-name="4e" unicode="&#xe04e;"
d="M300 800h200v-300h200l-300 -300l-300 300h200v300zM0 100h800v-100h-800v100z" />
<glyph glyph-name="4f" unicode="&#xe04f;"
d="M0 800h800v-100h-800v100zM400 600l300 -300h-200v-300h-200v300h-200z" />
<glyph glyph-name="50" unicode="&#xe050;"
d="M200 700h600v-600h-600l-200 300zM350 622l-72 -72l150 -150l-150 -150l72 -72l150 150l150 -150l72 72l-150 150l150 150l-72 72l-150 -150z" />
<glyph glyph-name="51" unicode="&#xe051;"
d="M400 700c220 0 400 -180 400 -400h-100c0 166 -134 300 -300 300s-300 -134 -300 -300h-100c0 220 180 400 400 400zM341 491l59 -88l59 88c81 -25 141 -101 141 -191c0 -110 -90 -200 -200 -200s-200 90 -200 200c0 90 60 166 141 191z" />
<glyph glyph-name="52" unicode="&#xe052;"
d="M0 800h300v-400h400v-400h-700v800zM400 800l300 -300h-300v300zM100 600v-100h100v100h-100zM100 400v-100h100v100h-100zM100 200v-100h400v100h-400z" />
<glyph glyph-name="53" unicode="&#xe053;" horiz-adv-x="600"
d="M200 700h100v-100h75c30 0 58 -6 81 -22s44 -44 44 -78v-100h-100v94c-4 3 -13 6 -25 6h-250c-14 0 -25 -11 -25 -25v-50c0 -15 20 -40 34 -44l257 -65c66 -16 109 -73 109 -141v-50c0 -68 -57 -125 -125 -125h-75v-100h-100v100h-75c-30 0 -58 6 -81 22s-44 44 -44 78
v100h100v-94c4 -3 13 -6 25 -6h250c14 0 25 11 25 25v50c0 15 -20 40 -34 44l-257 65c-66 16 -109 73 -109 141v50c0 68 57 125 125 125h75v100z" />
<glyph glyph-name="54" unicode="&#xe054;"
d="M0 700h300v-300l-300 -300v600zM500 700h300v-300l-300 -300v600z" />
<glyph glyph-name="55" unicode="&#xe055;"
d="M300 700v-600h-300v300zM800 700v-600h-300v300z" />
<glyph glyph-name="56" unicode="&#xe056;"
d="M300 700v-100c-111 0 -200 -89 -200 -200h200v-300h-300v300c0 165 135 300 300 300zM800 700v-100c-111 0 -200 -89 -200 -200h200v-300h-300v300c0 165 135 300 300 300z" />
<glyph glyph-name="57" unicode="&#xe057;"
d="M0 700h300v-300c0 -165 -135 -300 -300 -300v100c111 0 200 89 200 200h-200v300zM500 700h300v-300c0 -165 -135 -300 -300 -300v100c111 0 200 89 200 200h-200v300z" />
<glyph glyph-name="58" unicode="&#xe058;" horiz-adv-x="600"
d="M300 800l34 -34c11 -11 266 -270 266 -488c0 -165 -135 -300 -300 -300s-300 135 -300 300c0 218 255 477 266 488zM150 328c-28 0 -50 -22 -50 -50c0 -110 90 -200 200 -200c28 0 50 22 50 50s-22 50 -50 50c-55 0 -100 45 -100 100c0 28 -22 50 -50 50z" />
<glyph glyph-name="59" unicode="&#xe059;"
d="M400 800l400 -500h-800zM0 200h800v-200h-800v200z" />
<glyph glyph-name="5a" unicode="&#xe05a;" horiz-adv-x="600"
d="M300 800l300 -300h-600zM0 300h600l-300 -300z" />
<glyph glyph-name="5b" unicode="&#xe05b;"
d="M0 500h200v-200h-200v200zM300 500h200v-200h-200v200zM600 500h200v-200h-200v200z" />
<glyph glyph-name="5c" unicode="&#xe05c;"
d="M0 700h800v-100l-400 -200l-400 200v100zM0 500l400 -200l400 200v-400h-800v400z" />
<glyph glyph-name="5d" unicode="&#xe05d;"
d="M400 800l400 -200v-600h-800v600zM400 688l-300 -150v-188l300 -150l300 150v188zM200 500h400v-100l-200 -100l-200 100v100z" />
<glyph glyph-name="5e" unicode="&#xe05e;"
d="M600 700c69 0 134 -19 191 -50l-16 -106c-49 35 -109 56 -175 56c-131 0 -240 -84 -281 -200h331l-16 -100h-334c0 -36 8 -68 19 -100h297l-16 -100h-222c55 -61 133 -100 222 -100c78 0 147 30 200 78v-122c-59 -35 -127 -56 -200 -56c-147 0 -274 82 -344 200h-256
l19 100h197c-8 32 -16 66 -16 100h-200l25 100h191c45 172 198 300 384 300z" />
<glyph glyph-name="5f" unicode="&#xe05f;"
d="M0 700h700v-100h-700v100zM0 500h500v-100h-500v100zM0 300h800v-100h-800v100zM0 100h100v-100h-100v100zM200 100h100v-100h-100v100zM400 100h100v-100h-100v100z" />
<glyph glyph-name="60" unicode="&#xe060;"
d="M0 800h800v-100h-800v100zM200 600h400l-200 -200zM0 200h800v-200h-800v200z" />
<glyph glyph-name="61" unicode="&#xe061;"
d="M0 800h100v-800h-100v800zM600 800h200v-800h-200v800zM200 600l200 -200l-200 -200v400z" />
<glyph glyph-name="62" unicode="&#xe062;"
d="M0 800h200v-800h-200v800zM700 800h100v-800h-100v800zM600 600v-400l-200 200z" />
<glyph glyph-name="63" unicode="&#xe063;"
d="M0 800h800v-200h-800v200zM400 400l200 -200h-400zM0 100h800v-100h-800v100z" />
<glyph glyph-name="64" unicode="&#xe064;"
d="M0 800h200v-100h-100v-600h600v100h100v-200h-800v800zM400 800h400v-400l-150 150l-250 -250l-100 100l250 250z" />
<glyph glyph-name="65" unicode="&#xe065;"
d="M403 700c247 0 397 -300 397 -300s-150 -300 -397 -300c-253 0 -403 300 -403 300s150 300 403 300zM400 600c-110 0 -200 -90 -200 -200s90 -200 200 -200s200 90 200 200s-90 200 -200 200zM400 500c10 0 19 -3 28 -6c-16 -8 -28 -24 -28 -44c0 -28 22 -50 50 -50
c20 0 36 12 44 28c3 -9 6 -18 6 -28c0 -55 -45 -100 -100 -100s-100 45 -100 100s45 100 100 100z" />
<glyph glyph-name="66" unicode="&#xe066;" horiz-adv-x="900"
d="M331 700h3h3c3 1 7 1 10 1c12 0 29 -8 37 -17l94 -93l66 65c57 57 155 57 212 0c58 -58 58 -154 0 -212l-65 -66l93 -94c10 -8 18 -25 18 -38c0 -28 -22 -50 -50 -50c-13 0 -32 9 -40 20l-62 65l-381 -381h-269v272l375 381l-63 63c-9 8 -16 24 -16 36c0 20 16 42 35 48z
M447 481l-313 -315l128 -132l316 316z" />
<glyph glyph-name="67" unicode="&#xe067;"
d="M0 800h300v-400h400v-400h-700v800zM400 800l300 -300h-300v300z" />
<glyph glyph-name="68" unicode="&#xe068;"
d="M200 800c0 0 200 -100 200 -300s-298 -302 -200 -500c0 0 -200 100 -200 300s300 300 200 500zM500 500c0 0 200 -100 200 -300c0 -150 -60 -200 -100 -200h-300c0 200 300 300 200 500z" />
<glyph glyph-name="69" unicode="&#xe069;"
d="M0 800h100v-800h-100v800zM200 800h300v-100h300l-200 -203l200 -197h-400v100h-200v400z" />
<glyph glyph-name="6a" unicode="&#xe06a;" horiz-adv-x="400"
d="M150 800h150l-100 -200h200l-150 -300h150l-300 -300l-100 300h134l66 200h-200z" />
<glyph glyph-name="6b" unicode="&#xe06b;"
d="M0 800h300v-100h500v-100h-800v200zM0 500h800v-450c0 -28 -22 -50 -50 -50h-700c-28 0 -50 22 -50 50v450z" />
<glyph glyph-name="6c" unicode="&#xe06c;"
d="M150 800c83 0 150 -67 150 -150c0 -66 -41 -121 -100 -141v-118c15 5 33 9 50 9h200c28 0 50 22 50 50v59c-59 20 -100 75 -100 141c0 83 67 150 150 150s150 -67 150 -150c0 -66 -41 -121 -100 -141v-59c0 -82 -68 -150 -150 -150h-200c-14 0 -25 -7 -34 -16
c50 -24 84 -74 84 -134c0 -83 -67 -150 -150 -150s-150 67 -150 150c0 66 41 121 100 141v218c-59 20 -100 75 -100 141c0 83 67 150 150 150z" />
<glyph glyph-name="6d" unicode="&#xe06d;"
d="M0 800h400l-150 -150l150 -150l-100 -100l-150 150l-150 -150v400zM500 400l150 -150l150 150v-400h-400l150 150l-150 150z" />
<glyph glyph-name="6e" unicode="&#xe06e;"
d="M100 800l150 -150l150 150v-400h-400l150 150l-150 150zM400 400h400l-150 -150l150 -150l-100 -100l-150 150l-150 -150v400z" />
<glyph glyph-name="6f" unicode="&#xe06f;"
d="M400 800c221 0 400 -179 400 -400s-179 -400 -400 -400s-400 179 -400 400s179 400 400 400zM400 700c-56 0 -108 -17 -153 -44l22 -19c33 -18 13 -48 -13 -59c-30 -13 -77 10 -65 -41c13 -55 -27 -3 -47 -15c-42 -26 49 -152 31 -156l-59 34c-8 0 -13 -5 -16 -10
c1 -30 10 -57 19 -84c28 -11 77 -2 100 -25c47 -28 97 -115 75 -159c34 -13 68 -22 106 -22c101 0 193 48 247 125c3 24 -8 44 -50 44c-69 0 -156 13 -153 97c2 46 101 108 66 143c-30 30 12 39 12 66c0 37 -65 32 -69 50s20 36 41 56c-30 10 -60 19 -94 19zM631 591
c-38 -11 -94 -35 -87 -53c6 -15 52 -1 65 -13c11 -10 16 -59 44 -31l22 22v3c-11 26 -26 50 -44 72z" />
<glyph glyph-name="70" unicode="&#xe070;"
d="M703 800l97 -100l-400 -400l-100 100l-200 -203l-100 100l300 303l100 -100zM0 100h800v-100h-800v100z" />
<glyph glyph-name="71" unicode="&#xe071;"
d="M0 700h100v-100h-100v100zM200 700h100v-100h-100v100zM400 700h100v-100h-100v100zM600 700h100v-100h-100v100zM0 500h100v-100h-100v100zM200 500h100v-100h-100v100zM400 500h100v-100h-100v100zM600 500h100v-100h-100v100zM0 300h100v-100h-100v100zM200 300h100
v-100h-100v100zM400 300h100v-100h-100v100zM600 300h100v-100h-100v100zM0 100h100v-100h-100v100zM200 100h100v-100h-100v100zM400 100h100v-100h-100v100zM600 100h100v-100h-100v100z" />
<glyph glyph-name="72" unicode="&#xe072;"
d="M0 800h200v-200h-200v200zM300 800h200v-200h-200v200zM600 800h200v-200h-200v200zM0 500h200v-200h-200v200zM300 500h200v-200h-200v200zM600 500h200v-200h-200v200zM0 200h200v-200h-200v200zM300 200h200v-200h-200v200zM600 200h200v-200h-200v200z" />
<glyph glyph-name="73" unicode="&#xe073;"
d="M0 800h300v-300h-300v300zM500 800h300v-300h-300v300zM0 300h300v-300h-300v300zM500 300h300v-300h-300v300z" />
<glyph glyph-name="74" unicode="&#xe074;"
d="M19 800h662c11 0 19 -8 19 -19v-331c0 -28 -22 -50 -50 -50h-600c-28 0 -50 22 -50 50v331c0 11 8 19 19 19zM0 309c16 -6 32 -9 50 -9h600c18 0 34 3 50 9v-290c0 -11 -8 -19 -19 -19h-662c-11 0 -19 8 -19 19v290zM550 200c-28 0 -50 -22 -50 -50s22 -50 50 -50
s50 22 50 50s-22 50 -50 50z" />
<glyph glyph-name="75" unicode="&#xe075;"
d="M0 700h300v-100h-50c-28 0 -50 -22 -50 -50v-150h300v150c0 28 -22 50 -50 50h-50v100h300v-100h-50c-28 0 -50 -22 -50 -50v-400c0 -28 22 -50 50 -50h50v-100h-300v100h50c28 0 50 22 50 50v150h-300v-150c0 -28 22 -50 50 -50h50v-100h-300v100h50c28 0 50 22 50 50
v400c0 28 -22 50 -50 50h-50v100z" />
<glyph glyph-name="76" unicode="&#xe076;"
d="M400 700c165 0 300 -135 300 -300v-100h50c28 0 50 -22 50 -50v-200c0 -28 -22 -50 -50 -50h-100c-28 0 -50 22 -50 50v350c0 111 -89 200 -200 200s-200 -89 -200 -200v-350c0 -28 -22 -50 -50 -50h-100c-28 0 -50 22 -50 50v200c0 28 22 50 50 50h50v100
c0 165 135 300 300 300z" />
<glyph glyph-name="77" unicode="&#xe077;"
d="M0 500c0 109 91 200 200 200s200 -91 200 -200c0 109 91 200 200 200s200 -91 200 -200c0 -55 -23 -105 -59 -141l-341 -340l-341 340c-36 36 -59 86 -59 141z" />
<glyph glyph-name="78" unicode="&#xe078;"
d="M400 700l400 -300l-100 3v-403h-200v200h-200v-200h-200v400h-100z" />
<glyph glyph-name="79" unicode="&#xe079;"
d="M0 800h800v-800h-800v800zM100 700v-300l100 100l400 -400h100v100l-200 200l100 100l100 -100v300h-600z" />
<glyph glyph-name="7a" unicode="&#xe07a;"
d="M19 800h762c11 0 19 -8 19 -19v-762c0 -11 -8 -19 -19 -19h-762c-11 0 -19 8 -19 19v762c0 11 8 19 19 19zM100 600v-300h100l100 -100h200l100 100h100v300h-600z" />
<glyph glyph-name="7b" unicode="&#xe07b;"
d="M200 600c80 0 142 -56 200 -122c58 66 119 122 200 122c131 0 200 -101 200 -200s-69 -200 -200 -200c-81 0 -142 56 -200 122c-58 -66 -121 -122 -200 -122c-131 0 -200 101 -200 200s69 200 200 200zM200 500c-74 0 -100 -54 -100 -100s26 -100 100 -100
c42 0 88 47 134 100c-46 53 -92 100 -134 100zM600 500c-43 0 -88 -47 -134 -100c46 -53 91 -100 134 -100c74 0 100 54 100 100s-26 100 -100 100z" />
<glyph glyph-name="7c" unicode="&#xe07c;" horiz-adv-x="400"
d="M300 800c55 0 100 -45 100 -100s-45 -100 -100 -100s-100 45 -100 100s45 100 100 100zM150 550c83 0 150 -69 150 -150c0 -66 -100 -214 -100 -250c0 -28 22 -50 50 -50s50 22 50 50h100c0 -83 -67 -150 -150 -150s-150 64 -150 150s100 222 100 250s-22 50 -50 50
s-50 -22 -50 -50h-100c0 83 67 150 150 150z" />
<glyph glyph-name="7d" unicode="&#xe07d;"
d="M200 800h500v-100h-122c-77 -197 -156 -392 -234 -588l-6 -12h162v-100h-500v100h122c77 197 156 392 234 588l7 12h-163v100z" />
<glyph glyph-name="7e" unicode="&#xe07e;"
d="M0 700h800v-100h-800v100zM0 500h800v-100h-800v100zM0 300h800v-100h-800v100zM100 100h600v-100h-600v100z" />
<glyph glyph-name="7f" unicode="&#xe07f;"
d="M0 700h800v-100h-800v100zM0 500h800v-100h-800v100zM0 300h800v-100h-800v100zM0 100h600v-100h-600v100z" />
<glyph glyph-name="80" unicode="&#xe080;"
d="M0 700h800v-100h-800v100zM0 500h800v-100h-800v100zM0 300h800v-100h-800v100zM200 100h600v-100h-600v100z" />
<glyph glyph-name="81" unicode="&#xe081;"
d="M550 800c138 0 250 -112 250 -250s-112 -250 -250 -250c-16 0 -32 0 -47 3l-3 -3v-100h-200v-200h-300v200l303 303c-3 15 -3 31 -3 47c0 138 112 250 250 250zM600 700c-55 0 -100 -45 -100 -100s45 -100 100 -100s100 45 100 100s-45 100 -100 100z" />
<glyph glyph-name="82" unicode="&#xe082;"
d="M134 600h3h4h4h5h500c28 0 50 -22 50 -50v-350h100v-150c0 -28 -22 -50 -50 -50h-700c-28 0 -50 22 -50 50v150h100v350v2c0 20 15 42 34 48zM200 500v-300h100v-100h200v100h100v300h-400z" />
<glyph glyph-name="83" unicode="&#xe083;"
d="M0 800h400v-400h-400v400zM500 600h100v-400h-400v100h300v300zM700 400h100v-400h-400v100h300v300z" />
<glyph glyph-name="84" unicode="&#xe084;" horiz-adv-x="600"
d="M337 694c6 4 12 7 21 7c28 0 50 -22 50 -50c0 -17 -12 -37 -27 -45l-300 -150c-8 -6 -21 -11 -31 -11c-28 0 -50 22 -50 50c0 21 16 44 37 49zM437 544c6 4 12 7 21 7c28 0 50 -22 50 -50c0 -17 -12 -37 -27 -45l-400 -200c-8 -6 -21 -11 -31 -11c-28 0 -50 22 -50 50
c0 21 16 44 37 49zM437 344c6 4 12 7 21 7c28 0 50 -22 50 -50c0 -17 -12 -37 -27 -45l-106 -56c24 -4 43 -26 43 -50c0 -28 -23 -51 -51 -51c-2 0 -6 1 -8 1h-200c-26 1 -48 24 -48 50c0 16 12 36 26 44zM151 -50c0 23 20 50 46 50h3h4h5h100c28 0 50 -22 50 -50
s-22 -50 -50 -50h-100c-2 0 -6 -1 -8 -1c-28 0 -50 23 -50 51z" />
<glyph glyph-name="85" unicode="&#xe085;"
d="M199 800h100v-200h-200v100h100v100zM586 797h1c18 1 38 1 56 -3c36 -8 69 -26 97 -54c78 -78 78 -203 0 -281l-150 -150c-8 -13 -28 -24 -43 -24c-28 0 -50 22 -50 50c0 15 11 35 24 43l150 150c40 40 39 105 0 144c-41 41 -110 34 -144 0l-44 -44
c-8 -13 -27 -24 -42 -24c-28 0 -50 22 -50 50c0 15 11 35 24 43l43 44c32 33 72 53 128 56zM208 490c4 5 14 16 22 16h3c2 0 6 1 8 1c28 0 50 -22 50 -50c0 -11 -6 -27 -14 -35l-150 -150c-40 -40 -39 -105 0 -144c41 -41 110 -34 144 0l44 44c8 13 27 24 42 24
c28 0 50 -22 50 -50c0 -15 -11 -35 -24 -43l-43 -44c-22 -22 -48 -37 -75 -47c-70 -25 -151 -9 -207 47c-78 78 -78 203 0 281zM499 200h200v-100h-100v-100h-100v200z" />
<glyph glyph-name="86" unicode="&#xe086;"
d="M586 797c18 1 39 1 57 -3c36 -8 69 -26 97 -54c78 -78 78 -203 0 -281l-150 -150c-62 -62 -132 -81 -182 -78s-69 17 -84 25s-26 27 -26 44c0 28 22 51 50 51c8 0 19 -3 26 -7c0 0 15 -11 41 -13s62 3 106 47l150 150c40 40 39 105 0 144c-41 41 -110 34 -144 0
c-8 -13 -28 -24 -43 -24c-28 0 -50 22 -50 50c0 15 11 35 24 43c32 33 72 53 128 56zM386 566c50 -2 64 -17 85 -22s37 -28 37 -49c0 -28 -22 -50 -50 -50c-10 0 -23 5 -31 11c0 0 -19 9 -47 10s-63 -4 -103 -44l-150 -150c-40 -40 -39 -105 0 -144c41 -41 110 -34 144 0
c8 13 27 24 42 24c28 0 50 -22 50 -50c0 -15 -10 -35 -23 -43c-22 -22 -48 -37 -75 -47c-70 -25 -151 -9 -207 47c-78 78 -78 203 0 281l150 150c60 60 128 78 178 76z" />
<glyph glyph-name="87" unicode="&#xe087;"
d="M0 700h300v-300h-300v300zM400 700h400v-100h-400v100zM400 500h300v-100h-300v100zM0 300h300v-300h-300v300zM400 300h400v-100h-400v100zM400 100h300v-100h-300v100z" />
<glyph glyph-name="88" unicode="&#xe088;"
d="M50 700c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM200 700h600v-100h-600v100zM50 500c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM200 500h600v-100h-600v100zM50 300c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50
s22 50 50 50zM200 300h600v-100h-600v100zM50 100c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM200 100h600v-100h-600v100z" />
<glyph glyph-name="89" unicode="&#xe089;"
d="M800 800l-400 -800l-100 300l-300 100z" />
<glyph glyph-name="8a" unicode="&#xe08a;" horiz-adv-x="600"
d="M300 700c110 0 200 -90 200 -200v-100h100v-400h-600v400h100v100c0 110 90 200 200 200zM300 600c-56 0 -100 -44 -100 -100v-100h200v100c0 56 -44 100 -100 100z" />
<glyph glyph-name="8b" unicode="&#xe08b;" horiz-adv-x="600"
d="M300 800c110 0 200 -90 200 -200v-200h100v-400h-600v400h400v200c0 56 -44 100 -100 100s-100 -44 -100 -100h-100c0 110 90 200 200 200z" />
<glyph glyph-name="8c" unicode="&#xe08c;"
d="M400 700v-100c-111 0 -200 -89 -200 -200h100l-150 -200l-150 200h100c0 165 135 300 300 300zM650 600l150 -200h-100c0 -165 -135 -300 -300 -300v100c111 0 200 89 200 200h-100z" />
<glyph glyph-name="8d" unicode="&#xe08d;"
d="M100 800h600v-300h100l-150 -250l-150 250h100v200h-400v-100h-100v200zM150 550l150 -250h-100v-200h400v100h100v-200h-600v300h-100z" />
<glyph glyph-name="8e" unicode="&#xe08e;"
d="M600 700l200 -150l-200 -150v100h-500v-100h-100v100c0 55 45 100 100 100h500v100zM200 300v-100h500v100h100v-100c0 -55 -45 -100 -100 -100h-500v-100l-200 150z" />
<glyph glyph-name="8f" unicode="&#xe08f;" horiz-adv-x="900"
d="M350 800c193 0 350 -157 350 -350c0 -60 -17 -117 -44 -166c5 -3 12 -8 16 -12l100 -100c16 -16 30 -49 30 -72c0 -56 -46 -102 -102 -102c-23 0 -56 14 -72 30l-100 100c-4 3 -9 9 -12 13c-49 -26 -107 -41 -166 -41c-193 0 -350 157 -350 350s157 350 350 350zM350 200
c142 0 250 108 250 250c0 139 -111 250 -250 250s-250 -111 -250 -250s111 -250 250 -250z" />
<glyph glyph-name="90" unicode="&#xe090;" horiz-adv-x="600"
d="M300 800c166 0 300 -134 300 -300c0 -200 -300 -500 -300 -500s-300 300 -300 500c0 166 134 300 300 300zM300 700c-110 0 -200 -90 -200 -200s90 -200 200 -200s200 90 200 200s-90 200 -200 200z" />
<glyph glyph-name="91" unicode="&#xe091;" horiz-adv-x="900"
d="M0 800h800v-541c1 -3 1 -8 1 -11s0 -7 -1 -10v-238h-800v800zM495 250c0 26 22 50 50 50h5h150v400h-600v-600h600v100h-150h-5c-28 0 -50 22 -50 50zM350 600c83 0 150 -67 150 -150c0 -100 -150 -250 -150 -250s-150 150 -150 250c0 83 67 150 150 150zM350 500
c-28 0 -50 -22 -50 -50s22 -50 50 -50s50 22 50 50s-22 50 -50 50z" />
<glyph glyph-name="92" unicode="&#xe092;" horiz-adv-x="600"
d="M0 700h200v-600h-200v600zM400 700h200v-600h-200v600z" />
<glyph glyph-name="93" unicode="&#xe093;" horiz-adv-x="600"
d="M0 700l600 -300l-600 -300v600z" />
<glyph glyph-name="94" unicode="&#xe094;" horiz-adv-x="600"
d="M300 700c166 0 300 -134 300 -300s-134 -300 -300 -300s-300 134 -300 300s134 300 300 300z" />
<glyph glyph-name="95" unicode="&#xe095;"
d="M400 700v-600l-400 300zM400 400l400 300v-600z" />
<glyph glyph-name="96" unicode="&#xe096;"
d="M0 700l400 -300l-400 -300v600zM400 100v600l400 -300z" />
<glyph glyph-name="97" unicode="&#xe097;"
d="M0 700h200v-600h-200v600zM200 400l500 300v-600z" />
<glyph glyph-name="98" unicode="&#xe098;"
d="M0 700l500 -300l-500 -300v600zM500 100v600h200v-600h-200z" />
<glyph glyph-name="99" unicode="&#xe099;" horiz-adv-x="600"
d="M0 700h600v-600h-600v600z" />
<glyph glyph-name="9a" unicode="&#xe09a;"
d="M200 800h400v-200h200v-400h-200v-200h-400v200h-200v400h200v200z" />
<glyph glyph-name="9b" unicode="&#xe09b;"
d="M0 700h800v-100h-800v100zM0 403h800v-100h-800v100zM0 103h800v-100h-800v100z" />
<glyph glyph-name="9c" unicode="&#xe09c;" horiz-adv-x="600"
d="M278 700c7 2 13 4 22 4c55 0 100 -45 100 -100v-4v-200c0 -55 -45 -100 -100 -100s-100 45 -100 100v200v2c0 44 35 88 78 98zM34 500h4h3c3 0 6 1 9 1c28 0 50 -22 50 -50v-1v-50c0 -111 89 -200 200 -200s200 89 200 200v50c0 28 22 50 50 50s50 -22 50 -50v-50
c0 -148 -109 -270 -250 -294v-106h50c55 0 100 -45 100 -100h-400c0 55 45 100 100 100h50v106c-141 24 -250 146 -250 294v50v2c0 20 15 42 34 48z" />
<glyph glyph-name="9d" unicode="&#xe09d;"
d="M0 500h800v-200h-800v200z" />
<glyph glyph-name="9e" unicode="&#xe09e;"
d="M34 700h4h3h4h5h700c28 0 50 -22 50 -50v-500c0 -28 -22 -50 -50 -50h-250v-100h100c55 0 100 -45 100 -100h-600c0 55 45 100 100 100h100v100h-250c-28 0 -50 22 -50 50v500v2c0 20 15 42 34 48zM100 600v-400h600v400h-600z" />
<glyph glyph-name="9f" unicode="&#xe09f;"
d="M272 700c-14 -40 -22 -83 -22 -128c0 -221 179 -400 400 -400c45 0 88 8 128 22c-53 -158 -202 -272 -378 -272c-221 0 -400 179 -400 400c0 176 114 325 272 378z" />
<glyph glyph-name="a0" unicode="&#xe0a0;"
d="M350 700l150 -150h-100v-150h150v100l150 -150l-150 -150v100h-150v-150h100l-150 -150l-150 150h100v150h-150v-100l-150 150l150 150v-100h150v150h-100z" />
<glyph glyph-name="a1" unicode="&#xe0a1;"
d="M800 800v-550c0 -83 -67 -150 -150 -150s-150 67 -150 150s67 150 150 150c17 0 35 -4 50 -9v206c-201 -6 -327 -27 -400 -50v-397c0 -83 -67 -150 -150 -150s-150 67 -150 150s67 150 150 150c17 0 35 -4 50 -9v409s100 100 600 100z" />
<glyph glyph-name="a2" unicode="&#xe0a2;" horiz-adv-x="700"
d="M499 700c51 0 102 -20 141 -59c78 -78 78 -203 0 -281l-250 -244c-48 -48 -127 -48 -175 0s-48 127 0 175l96 97l69 -69l-90 -94l-7 -3c-10 -10 -10 -28 0 -38s28 -10 38 0l250 247c37 40 39 102 0 141s-104 40 -144 0l-278 -275c-66 -69 -68 -179 0 -247
c69 -69 181 -69 250 0l9 12l116 113l69 -69l-125 -125c-107 -107 -281 -107 -388 0s-107 281 0 388l278 272c39 39 90 59 141 59z" />
<glyph glyph-name="a3" unicode="&#xe0a3;"
d="M600 800l200 -200l-100 -100l-200 200zM400 600l200 -200l-400 -400h-200v200z" />
<glyph glyph-name="a4" unicode="&#xe0a4;"
d="M550 800c83 0 150 -90 150 -200s-67 -200 -150 -200c-22 0 -40 8 -59 19c6 26 9 52 9 81c0 84 -27 158 -72 212c27 52 71 88 122 88zM250 700c83 0 150 -90 150 -200s-67 -200 -150 -200s-150 90 -150 200s67 200 150 200zM725 384c44 -22 75 -66 75 -118v-166h-200v66
c0 50 -17 96 -44 134c66 2 126 33 169 84zM75 284c45 -53 106 -84 175 -84s130 31 175 84c44 -22 75 -66 75 -118v-166h-500v166c0 52 31 96 75 118z" />
<glyph glyph-name="a5" unicode="&#xe0a5;"
d="M400 800c110 0 200 -112 200 -250s-90 -250 -200 -250s-200 112 -200 250s90 250 200 250zM191 300c54 -61 128 -100 209 -100s155 39 209 100c106 -5 191 -92 191 -200v-100h-800v100c0 108 85 195 191 200z" />
<glyph glyph-name="a6" unicode="&#xe0a6;" horiz-adv-x="600"
d="M19 800h462c11 0 19 -8 19 -19v-762c0 -11 -8 -19 -19 -19h-462c-11 0 -19 8 -19 19v762c0 11 8 19 19 19zM100 700v-500h300v500h-300zM250 150c-28 0 -50 -22 -50 -50s22 -50 50 -50s50 22 50 50s-22 50 -50 50z" />
<glyph glyph-name="a7" unicode="&#xe0a7;"
d="M350 800c17 0 34 -1 50 -3v-397l-297 297c63 64 150 103 247 103zM500 694c169 -25 300 -168 300 -344c0 -193 -157 -350 -350 -350c-85 0 -161 31 -222 81l272 272v341zM91 562l237 -234l-212 -212c-70 55 -116 138 -116 234c0 84 35 158 91 212z" />
<glyph glyph-name="a8" unicode="&#xe0a8;"
d="M92 650c0 23 20 50 46 50h3h4h5h400c28 0 50 -22 50 -50s-22 -50 -50 -50h-50v-200h100c55 0 100 -45 100 -100h-300v-300l-56 -100l-44 100v300h-300c0 55 45 100 100 100h100v200h-50c-2 0 -6 -1 -8 -1c-28 0 -50 23 -50 51z" />
<glyph glyph-name="a9" unicode="&#xe0a9;"
d="M400 800c221 0 400 -179 400 -400s-179 -400 -400 -400s-400 179 -400 400s179 400 400 400zM300 600v-400l300 200z" />
<glyph glyph-name="aa" unicode="&#xe0aa;"
d="M300 800h200v-300h300v-200h-300v-300h-200v300h-300v200h300v300z" />
<glyph glyph-name="ab" unicode="&#xe0ab;"
d="M300 800h100v-400h-100v400zM172 656l62 -78l-40 -31c-58 -46 -94 -117 -94 -197c0 -139 111 -250 250 -250s250 111 250 250c0 80 -39 151 -97 197l-37 31l62 78l38 -31c82 -64 134 -164 134 -275c0 -193 -157 -350 -350 -350s-350 157 -350 350c0 111 53 211 134 275z
" />
<glyph glyph-name="ac" unicode="&#xe0ac;"
d="M200 800h400v-200h-400v200zM9 500h782c6 0 9 -3 9 -9v-282c0 -6 -3 -9 -9 -9h-91v200h-600v-200h-91c-6 0 -9 3 -9 9v282c0 6 3 9 9 9zM200 300h400v-300h-400v300z" />
<glyph glyph-name="ad" unicode="&#xe0ad;"
d="M0 700h100v-700h-100v700zM700 700h100v-700h-100v700zM200 600h200v-100h-200v100zM300 400h200v-100h-200v100zM400 200h200v-100h-200v100z" />
<glyph glyph-name="ae" unicode="&#xe0ae;"
d="M325 700c42 -141 87 -280 131 -419c29 74 59 148 88 222c30 -57 58 -114 87 -172h169v-100h-231l-13 28c-37 -92 -74 -184 -112 -275c-38 129 -79 257 -119 385c-42 -133 -83 -267 -125 -400c-28 88 -56 175 -84 262h-116v100h188l9 -34l3 -6c42 137 83 273 125 409z" />
<glyph glyph-name="af" unicode="&#xe0af;"
d="M200 600c0 57 43 100 100 100s100 -43 100 -100c0 -28 -18 -48 -28 -72c-3 -6 -3 -16 -3 -28h231v-231c12 0 22 0 28 3c24 10 44 28 72 28c57 0 100 -43 100 -100s-43 -100 -100 -100c-28 0 -48 18 -72 28c-6 3 -16 3 -28 3v-231h-231c0 12 0 22 3 28c10 24 28 44 28 72
c0 57 -43 100 -100 100s-100 -43 -100 -100c0 -28 18 -48 28 -72c3 -6 3 -16 3 -28h-231v600h231c0 12 0 22 -3 28c-10 24 -28 44 -28 72z" />
<glyph glyph-name="b0" unicode="&#xe0b0;" horiz-adv-x="500"
d="M247 700c84 0 148 -20 191 -59s59 -93 59 -141c0 -117 -69 -181 -119 -225s-81 -67 -81 -150v-25h-100v25c0 117 65 181 115 225s85 67 85 150c0 25 -8 48 -28 66s-56 34 -122 34s-97 -18 -116 -37s-27 -43 -31 -69l-100 12c5 38 19 88 59 128s103 66 188 66zM197 0h100
v-100h-100v100z" />
<glyph glyph-name="b1" unicode="&#xe0b1;"
d="M450 800c138 0 250 -112 250 -250v-50c58 -21 100 -85 100 -150c0 -69 -48 -127 -112 -144c-22 55 -75 94 -138 94c-20 0 -39 -5 -56 -12c-17 64 -75 112 -144 112s-127 -48 -144 -112c-17 7 -36 12 -56 12c-37 0 -71 -12 -97 -34c-33 36 -53 82 -53 134
c0 110 90 200 200 200c23 114 129 200 250 200zM334 300h4h3c3 0 6 1 9 1c28 0 50 -22 50 -50v-1v-200c0 -28 -22 -50 -50 -50s-50 22 -50 50v200v2c0 20 15 42 34 48zM134 200h4h3c3 0 6 1 9 1c28 0 50 -22 50 -50v-1v-100c0 -28 -22 -50 -50 -50s-50 22 -50 50v100v2
c0 20 15 42 34 48zM534 200h3h4c3 0 6 1 9 1c28 0 50 -22 50 -50v-1v-100c0 -28 -22 -50 -50 -50s-50 22 -50 50v100v2c0 20 15 42 34 48z" />
<glyph glyph-name="b2" unicode="&#xe0b2;"
d="M600 800l200 -150l-200 -150v100h-50l-153 -191l175 -206l6 -3h22v100l200 -150l-200 -150v100h-25c-35 0 -56 12 -78 38l-166 190l-153 -190c-22 -27 -43 -38 -78 -38h-100v100h100l166 206l-163 191l-3 3h-100v100h100c34 0 56 -12 78 -38l153 -178l141 178
c22 27 43 38 78 38h50v100z" />
<glyph glyph-name="b3" unicode="&#xe0b3;"
d="M400 800c110 0 209 -47 281 -119l119 119v-300h-300l109 109c-54 55 -126 91 -209 91c-166 0 -300 -134 -300 -300s134 -300 300 -300c83 0 158 34 212 88l72 -72c-72 -72 -174 -116 -284 -116c-220 0 -400 180 -400 400s180 400 400 400z" />
<glyph glyph-name="b4" unicode="&#xe0b4;"
d="M400 800h400v-400l-166 166l-400 -400l166 -166h-400v400l166 -166l400 400z" />
<glyph glyph-name="b5" unicode="&#xe0b5;" horiz-adv-x="600"
d="M250 800l250 -300h-200v-200h200l-250 -300l-250 300h200v200h-200z" />
<glyph glyph-name="b6" unicode="&#xe0b6;"
d="M300 600v-200h200v200l300 -250l-300 -250v200h-200v-200l-300 250z" />
<glyph glyph-name="b7" unicode="&#xe0b7;"
d="M0 800c441 0 800 -359 800 -800h-200c0 333 -267 600 -600 600v200zM0 500c275 0 500 -225 500 -500h-200c0 167 -133 300 -300 300v200zM0 200c110 0 200 -90 200 -200h-200v200z" />
<glyph glyph-name="b8" unicode="&#xe0b8;"
d="M100 800c386 0 700 -314 700 -700h-100c0 332 -268 600 -600 600v100zM100 600c276 0 500 -224 500 -500h-100c0 222 -178 400 -400 400v100zM100 400c165 0 300 -135 300 -300h-100c0 111 -89 200 -200 200v100zM100 200c55 0 100 -45 100 -100s-45 -100 -100 -100
s-100 45 -100 100s45 100 100 100z" />
<glyph glyph-name="b9" unicode="&#xe0b9;"
d="M300 800h400c55 0 100 -45 100 -100v-200h-400v150c0 28 -22 50 -50 50s-50 -22 -50 -50v-250h400v-300c0 -55 -45 -100 -100 -100h-500c-55 0 -100 45 -100 100v200h100v-150c0 -28 22 -50 50 -50s50 22 50 50v550c0 55 45 100 100 100z" />
<glyph glyph-name="ba" unicode="&#xe0ba;"
d="M75 700h225v-100h-200v-500h400v100h100v-125c0 -41 -34 -75 -75 -75h-450c-41 0 -75 34 -75 75v550c0 41 34 75 75 75zM600 700l200 -200l-200 -200v100h-200c-94 0 -173 -65 -194 -153c23 199 189 353 394 353v100z" />
<glyph glyph-name="bb" unicode="&#xe0bb;"
d="M500 700l300 -284l-300 -316v200h-100c-200 0 -348 -102 -400 -300c0 295 100 500 500 500v200z" />
<glyph glyph-name="bc" unicode="&#xe0bc;"
d="M381 791l19 9l19 -9c127 -53 253 -108 381 -160v-31c0 -166 -67 -313 -147 -419c-40 -53 -83 -97 -125 -128s-82 -53 -128 -53s-86 22 -128 53s-85 75 -125 128c-80 107 -147 253 -147 419v31c128 52 254 107 381 160zM400 100v591l-294 -122c8 -126 58 -243 122 -328
c35 -46 73 -86 106 -110s62 -31 66 -31z" />
<glyph glyph-name="bd" unicode="&#xe0bd;"
d="M600 800h100v-800h-100v800zM400 700h100v-700h-100v700zM200 500h100v-500h-100v500zM0 300h100v-300h-100v300z" />
<glyph glyph-name="be" unicode="&#xe0be;"
d="M300 800h100v-200h200l100 -100l-100 -100h-200v-400h-100v500h-200l-100 100l100 100h200v100z" />
<glyph glyph-name="bf" unicode="&#xe0bf;"
d="M200 800h100v-600h200l-250 -200l-250 200h200v600zM400 800h200v-100h-200v100zM400 600h300v-100h-300v100zM400 400h400v-100h-400v100z" />
<glyph glyph-name="c0" unicode="&#xe0c0;"
d="M200 800h100v-600h200l-250 -200l-250 200h200v600zM400 800h400v-100h-400v100zM400 600h300v-100h-300v100zM400 400h200v-100h-200v100z" />
<glyph glyph-name="c1" unicode="&#xe0c1;"
d="M75 700h650c41 0 75 -34 75 -75v-550c0 -41 -34 -75 -75 -75h-650c-41 0 -75 34 -75 75v550c0 41 34 75 75 75zM100 600v-100h100v100h-100zM300 600v-100h400v100h-400zM100 400v-100h100v100h-100zM300 400v-100h400v100h-400zM100 200v-100h100v100h-100zM300 200
v-100h400v100h-400z" />
<glyph glyph-name="c2" unicode="&#xe0c2;"
d="M400 800l100 -300h300l-250 -200l100 -300l-250 200l-250 -200l100 300l-250 200h300z" />
<glyph glyph-name="c3" unicode="&#xe0c3;"
d="M400 800c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM150 700c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM650 700c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM400 600c110 0 200 -90 200 -200
s-90 -200 -200 -200s-200 90 -200 200s90 200 200 200zM50 450c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM750 450c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM150 200c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50
s22 50 50 50zM650 200c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM400 100c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50z" />
<glyph glyph-name="c4" unicode="&#xe0c4;"
d="M34 800h632c18 0 34 -16 34 -34v-732c0 -18 -16 -34 -34 -34h-632c-18 0 -34 16 -34 34v732c0 18 16 34 34 34zM100 700v-500h500v500h-500zM350 150c-38 0 -63 -42 -44 -75s69 -33 88 0s-6 75 -44 75z" />
<glyph glyph-name="c5" unicode="&#xe0c5;"
d="M0 800h300l500 -500l-300 -300l-500 500v300zM200 700c-55 0 -100 -45 -100 -100s45 -100 100 -100s100 45 100 100s-45 100 -100 100z" />
<glyph glyph-name="c6" unicode="&#xe0c6;"
d="M0 600h200l300 -300l-200 -200l-300 300v200zM340 600h160l300 -300l-200 -200l-78 78l119 122zM150 500c-28 0 -50 -22 -50 -50s22 -50 50 -50s50 22 50 50s-22 50 -50 50z" />
<glyph glyph-name="c7" unicode="&#xe0c7;"
d="M400 800c220 0 400 -180 400 -400s-180 -400 -400 -400s-400 180 -400 400s180 400 400 400zM400 700c-166 0 -300 -134 -300 -300s134 -300 300 -300s300 134 300 300s-134 300 -300 300zM400 600c110 0 200 -90 200 -200s-90 -200 -200 -200s-200 90 -200 200
s90 200 200 200zM400 500c-56 0 -100 -44 -100 -100s44 -100 100 -100s100 44 100 100s-44 100 -100 100z" />
<glyph glyph-name="c8" unicode="&#xe0c8;"
d="M0 700h559l-100 -100h-359v-500h500v159l100 100v-359h-700v700zM700 700l100 -100l-400 -400l-200 200l100 100l100 -100z" />
<glyph glyph-name="c9" unicode="&#xe0c9;"
d="M9 800h782c6 0 9 -3 9 -9v-782c0 -6 -3 -9 -9 -9h-782c-6 0 -9 3 -9 9v782c0 6 3 9 9 9zM150 722l-72 -72l100 -100l-100 -100l72 -72l172 172zM400 500v-100h300v100h-300z" />
<glyph glyph-name="ca" unicode="&#xe0ca;"
d="M0 800h800v-200h-50c0 55 -45 100 -100 100h-150v-550c0 -28 22 -50 50 -50h50v-100h-400v100h50c28 0 50 22 50 50v550h-150c-55 0 -100 -45 -100 -100h-50v200z" />
<glyph glyph-name="cb" unicode="&#xe0cb;"
d="M0 700h100v-400h-100v400zM200 700h350c21 0 39 -13 47 -31c0 0 103 -291 103 -319s-22 -50 -50 -50h-150c-28 0 -50 -25 -50 -50s39 -158 47 -184s-5 -55 -31 -63s-52 5 -66 31s-109 219 -128 238s-44 28 -72 28v400z" />
<glyph glyph-name="cc" unicode="&#xe0cc;"
d="M400 666c10 19 28 32 47 34l19 -3c26 -8 39 -37 31 -63s-47 -159 -47 -184s22 -50 50 -50h150c28 0 50 -22 50 -50s-103 -319 -103 -319c-8 -18 -26 -31 -47 -31h-350v400c28 0 53 9 72 28s114 212 128 238zM0 400h100v-400h-100v400z" />
<glyph glyph-name="cd" unicode="&#xe0cd;"
d="M200 700h300v-100h-100v-6c25 -4 50 -8 72 -16l-34 -94c-28 11 -58 16 -88 16c-139 0 -250 -111 -250 -250s111 -250 250 -250s250 111 250 250c0 31 -5 60 -16 88l91 37c14 -38 25 -81 25 -125c0 -193 -157 -350 -350 -350s-350 157 -350 350c0 176 130 323 300 347v3
h-100v100zM700 584c0 0 -296 -348 -316 -368s-48 -20 -68 0s-20 48 0 68s384 300 384 300z" />
<glyph glyph-name="ce" unicode="&#xe0ce;"
d="M600 700l200 -150l-200 -150v100h-600v100h600v100zM200 300v-100h600v-100h-600v-100l-200 150z" />
<glyph glyph-name="cf" unicode="&#xe0cf;"
d="M300 800h100c55 0 100 -45 100 -100h100c55 0 100 -45 100 -100h-700c0 55 45 100 100 100h100c0 55 45 100 100 100zM100 500h100v-350c0 -28 22 -50 50 -50s50 22 50 50v350h100v-350c0 -28 22 -50 50 -50s50 22 50 50v350h100v-481c0 -11 -8 -19 -19 -19h-462
c-11 0 -19 8 -19 19v481z" />
<glyph glyph-name="d0" unicode="&#xe0d0;"
d="M100 800h200v-400c0 -55 45 -100 100 -100s100 45 100 100v400h100v-400c0 -110 -90 -200 -200 -200h-50c-138 0 -250 90 -250 200v400zM0 100h700v-100h-700v100z" />
<glyph glyph-name="d1" unicode="&#xe0d1;"
d="M9 700h182c6 0 9 -3 9 -9v-482c0 -6 -3 -9 -9 -9h-182c-6 0 -9 3 -9 9v482c0 6 3 9 9 9zM609 700h182c6 0 9 -3 9 -9v-482c0 -6 -3 -9 -9 -9h-182c-6 0 -9 3 -9 9v482c0 6 3 9 9 9zM309 500h182c6 0 9 -3 9 -9v-282c0 -6 -3 -9 -9 -9h-182c-6 0 -9 3 -9 9v282
c0 6 3 9 9 9zM0 100h800v-100h-800v100z" />
<glyph glyph-name="d2" unicode="&#xe0d2;"
d="M10 700h181c6 0 9 -3 9 -9v-191h-200v191c0 6 4 9 10 9zM610 700h181c6 0 9 -3 9 -9v-191h-200v191c0 6 5 9 10 9zM310 600h181c6 0 9 -3 9 -9v-91h-200v91c0 6 4 9 10 9zM0 400h800v-100h-800v100zM0 200h200v-191c0 -6 -3 -9 -9 -9h-182c-6 0 -9 3 -9 9v191zM300 200
h200v-91c0 -6 -3 -9 -9 -9h-181c-6 0 -10 3 -10 9v91zM600 200h200v-191c0 -6 -3 -9 -9 -9h-181c-6 0 -10 3 -10 9v191z" />
<glyph glyph-name="d3" unicode="&#xe0d3;"
d="M0 700h800v-100h-800v100zM9 500h182c6 0 9 -3 9 -9v-482c0 -6 -3 -9 -9 -9h-182c-6 0 -9 3 -9 9v482c0 6 3 9 9 9zM309 500h182c6 0 9 -3 9 -9v-282c0 -6 -3 -9 -9 -9h-182c-6 0 -9 3 -9 9v282c0 6 3 9 9 9zM609 500h182c6 0 9 -3 9 -9v-482c0 -6 -3 -9 -9 -9h-182
c-6 0 -9 3 -9 9v482c0 6 3 9 9 9z" />
<glyph glyph-name="d4" unicode="&#xe0d4;"
d="M50 600h500c28 0 50 -22 50 -50v-150l100 100h100v-300h-100l-100 100v-150c0 -28 -22 -50 -50 -50h-500c-28 0 -50 22 -50 50v400c0 28 22 50 50 50z" />
<glyph glyph-name="d5" unicode="&#xe0d5;"
d="M334 800h66v-800h-66l-134 200h-200v400h200zM500 600v100c26 0 52 -4 75 -10c130 -33 225 -150 225 -290s-95 -258 -225 -291h-3c-23 -6 -47 -9 -72 -9v100c17 0 34 2 50 6c86 22 150 100 150 194s-64 172 -150 194c-16 4 -33 6 -50 6zM500 500l25 -3
c44 -11 75 -51 75 -97s-32 -86 -75 -97l-25 -3v200z" />
<glyph glyph-name="d6" unicode="&#xe0d6;" horiz-adv-x="600"
d="M334 800h66v-800h-66l-134 200h-200v400h200zM500 500l25 -3c44 -11 75 -51 75 -97s-32 -86 -75 -97l-25 -3v200z" />
<glyph glyph-name="d7" unicode="&#xe0d7;" horiz-adv-x="400"
d="M334 800h66v-800h-66l-134 200h-200v400h200z" />
<glyph glyph-name="d8" unicode="&#xe0d8;"
d="M309 800h82c6 0 10 -4 12 -9l294 -682l3 -19v-81c0 -6 -3 -9 -9 -9h-682c-6 0 -9 3 -9 9v81l3 19l294 682c2 5 6 9 12 9zM300 500v-200h100v200h-100zM300 200v-100h100v100h-100z" />
<glyph glyph-name="d9" unicode="&#xe0d9;"
d="M375 800c138 0 269 -39 378 -109l-53 -82c-93 60 -205 91 -325 91c-119 0 -229 -32 -322 -91l-53 82c109 70 237 109 375 109zM375 500c78 0 154 -23 216 -62l-53 -85c-46 30 -104 47 -163 47c-60 0 -112 -17 -159 -47l-54 85c62 40 134 62 213 62zM375 200
c55 0 100 -45 100 -100s-45 -100 -100 -100s-100 45 -100 100s45 100 100 100z" />
<glyph glyph-name="da" unicode="&#xe0da;" horiz-adv-x="900"
d="M551 800c16 0 32 0 47 -3l-97 -97v-200h200l97 97c3 -15 3 -31 3 -47c0 -138 -112 -250 -250 -250c-32 0 -62 8 -90 19l-288 -291c-20 -20 -46 -28 -72 -28s-52 8 -72 28c-39 39 -39 105 0 144l291 287c-11 28 -19 59 -19 91c0 138 112 250 250 250zM101 150
c-28 0 -50 -22 -50 -50s22 -50 50 -50s50 22 50 50s-22 50 -50 50z" />
<glyph glyph-name="db" unicode="&#xe0db;"
d="M141 700c84 -84 169 -167 253 -250c82 83 167 165 247 250l143 -141l-253 -253c84 -82 167 -166 253 -247l-143 -143c-81 86 -165 169 -247 253l-253 -253l-141 143c85 80 167 164 250 247c-83 84 -166 169 -250 253z" />
<glyph glyph-name="dc" unicode="&#xe0dc;"
d="M0 800h100l231 -300h38l231 300h100l-225 -300h225v-100h-300v-100h300v-100h-300v-200h-100v200h-300v100h300v100h-300v100h225z" />
<glyph glyph-name="dd" unicode="&#xe0dd;" horiz-adv-x="900"
d="M350 800c193 0 350 -157 350 -350c0 -61 -17 -119 -44 -169c4 -2 10 -6 13 -9l103 -100c16 -16 30 -49 30 -72c0 -56 -46 -102 -102 -102c-23 0 -56 14 -72 30l-100 103c-3 3 -7 9 -9 13c-50 -28 -108 -44 -169 -44c-193 0 -350 157 -350 350s157 350 350 350zM350 700
c-139 0 -250 -111 -250 -250s111 -250 250 -250c62 0 119 23 163 60c7 11 19 25 31 31l3 3c34 43 53 97 53 156c0 139 -111 250 -250 250zM300 600h100v-100h100v-100h-100v-100h-100v100h-100v100h100v100z" />
<glyph glyph-name="de" unicode="&#xe0de;" horiz-adv-x="900"
d="M350 800c193 0 350 -157 350 -350c0 -61 -17 -119 -44 -169c4 -2 10 -6 13 -9l103 -100c16 -16 30 -49 30 -72c0 -56 -46 -102 -102 -102c-23 0 -56 14 -72 30l-100 103c-3 3 -7 9 -9 13c-50 -28 -108 -44 -169 -44c-193 0 -350 157 -350 350s157 350 350 350zM350 700
c-139 0 -250 -111 -250 -250s111 -250 250 -250c62 0 119 23 163 60c7 11 19 25 31 31l3 3c34 43 53 97 53 156c0 139 -111 250 -250 250zM200 500h300v-100h-300v100z" />
</font>
</defs></svg>

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Binary file not shown.

34
public/js/app.js vendored

File diff suppressed because one or more lines are too long

3927
public/js/bootstrap.js vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

31
public/manifest.json Normal file
View File

@@ -0,0 +1,31 @@
{
"name": "Tissue",
"short_name": "Tissue",
"description": "気持ちよくティッシュを使った、そのあとの感想戦。",
"display": "minimal-ui",
"start_url": "/",
"theme_color": "#e53fb1",
"share_target": {
"action": "/checkin",
"method": "GET",
"enctype": "application/x-www-form-urlencoded",
"url_template": "/checkin?link={url}",
"params": {
"title": "",
"text": "link",
"url": "link"
}
},
"icons": [
{
"src": "apple-touch-icon.png",
"type": "image/png",
"sizes": "180x180"
},
{
"src": "chrome-touch-icon-192x192.png",
"type": "image/png",
"sizes": "192x192"
}
]
}

1
public/sw.js vendored Normal file
View File

@@ -0,0 +1 @@
self.addEventListener('fetch', function() {});

View File

@@ -1,22 +1,104 @@
/**
* First we will load all of this project's JavaScript dependencies which
* includes Vue and other libraries. It is a great starting point when
* building robust, powerful web applications using Vue and Laravel.
*/
import Cookies from 'js-cookie';
require('./bootstrap');
window.Vue = require('vue');
$(() => {
if (Cookies.get('agechecked')) {
$('body').removeClass('tis-need-agecheck');
} else {
$('#ageCheckModal')
.modal({backdrop: 'static'})
.on('hide.bs.modal', function () {
$('body').removeClass('tis-need-agecheck');
Cookies.set('agechecked', '1', {expires: 365});
});
}
/**
* Next, we will create a fresh Vue application instance and attach it to
* the page. Then, you may begin adding components to this application
* or customize the JavaScript scaffolding to fit your unique needs.
*/
if (navigator.serviceWorker) {
navigator.serviceWorker.register('/sw.js');
}
$('[data-toggle="tooltip"]').tooltip();
$('.alert').alert();
$('.tis-page-selector').pageSelector();
Vue.component('example', require('./components/Example.vue'));
$('.link-card').linkCard();
const $deleteCheckinModal = $('#deleteCheckinModal').deleteCheckinModal();
$(document).on('click', '[data-target="#deleteCheckinModal"]', function (event) {
event.preventDefault();
$deleteCheckinModal.modal('show', this);
});
const app = new Vue({
el: '#app'
$(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;
} else if (xhr.status === 401) {
alert('いいねするためにはログインしてください。');
return;
}
console.error(xhr);
alert('いいねできませんでした。');
});
}
});
$(document).on('click', '.card-spoiler-overlay', function (event) {
const $this = $(this);
$this.siblings(".card-link").removeClass("card-spoiler");
$this.remove();
});
});

View File

@@ -1,53 +1,17 @@
// jQuery
import './tissue';
window._ = require('lodash');
/**
* We'll load jQuery and the Bootstrap jQuery plugin which provides support
* for JavaScript based Bootstrap features such as modals and tabs. This
* code may be modified to fit the specific needs of your application.
*/
try {
window.$ = window.jQuery = require('jquery');
require('bootstrap-sass');
} catch (e) {}
/**
* We'll load the axios HTTP library which allows us to easily issue requests
* to our Laravel back-end. This library automatically handles sending the
* CSRF token as a header based on the value of the "XSRF" token cookie.
*/
window.axios = require('axios');
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
/**
* Next we will register the CSRF Token as a common header with Axios so that
* all outgoing HTTP requests automatically have it attached. This is just
* a simple convenience so we don't have to attach every token manually.
*/
let token = document.head.querySelector('meta[name="csrf-token"]');
if (token) {
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
} else {
// Setup global request header
const token = document.head.querySelector('meta[name="csrf-token"]');
if (!token) {
console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
}
/**
* Echo exposes an expressive API for subscribing to channels and listening
* for events that are broadcast by Laravel. Echo and event broadcasting
* allows your team to easily build robust real-time web applications.
*/
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': token.content
}
});
// import Echo from 'laravel-echo'
// window.Pusher = require('pusher-js');
// window.Echo = new Echo({
// broadcaster: 'pusher',
// key: 'your-pusher-key'
// });
// Bootstrap
import 'bootstrap';

View File

@@ -0,0 +1,86 @@
import Vue from 'vue';
import TagInput from "./components/TagInput.vue";
import MetadataPreview from './components/MetadataPreview.vue';
import GraphemeSplitter from "grapheme-splitter";
export const bus = new Vue({name: "EventBus"});
export enum MetadataLoadState {
Inactive,
Loading,
Success,
Failed,
}
new Vue({
el: '#app',
data: {
metadata: null,
metadataLoadState: MetadataLoadState.Inactive,
noteLength: 0
},
components: {
TagInput,
MetadataPreview
},
mounted() {
// オカズリンクにURLがセットされている場合は、すぐにメタデータを取得する
const linkInput = this.$el.querySelector<HTMLInputElement>("#link");
if (linkInput && /^https?:\/\//.test(linkInput.value)) {
this.fetchMetadata(linkInput.value);
}
},
watch: {
noteLength: (length: number) => {
const counter = document.querySelector<HTMLElement>(
"#note-character-counter"
);
if (counter) {
counter.innerText = `残り ${500 - length} 文字`;
}
}
},
methods: {
// オカズリンクの変更時
onChangeLink(event: Event) {
if (event.target instanceof HTMLInputElement) {
const url = event.target.value;
if (url.trim() === '' || !/^https?:\/\//.test(url)) {
this.metadata = null;
this.metadataLoadState = MetadataLoadState.Inactive;
return;
}
this.fetchMetadata(url);
}
},
onChangeNote(event: Event) {
if (event.target instanceof HTMLTextAreaElement) {
const splitter = new GraphemeSplitter();
this.noteLength = splitter.splitGraphemes(
event.target.value
).length;
}
},
// メタデータの取得
fetchMetadata(url: string) {
this.metadataLoadState = MetadataLoadState.Loading;
$.ajax({
url: '/api/checkin/card',
method: 'get',
type: 'json',
data: {
url
}
}).then(data => {
this.metadata = data;
this.metadataLoadState = MetadataLoadState.Success;
}).catch(e => {
this.metadata = null;
this.metadataLoadState = MetadataLoadState.Failed;
});
}
}
});

View File

@@ -1,23 +0,0 @@
<template>
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Example Component</div>
<div class="panel-body">
I'm an example component!
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
mounted() {
console.log('Component mounted.')
}
}
</script>

View File

@@ -0,0 +1,143 @@
<template>
<div class="form-row" v-if="state !== MetadataLoadState.Inactive">
<div class="form-group col-sm-12">
<div class="card link-card-mini mb-2 px-0">
<div v-if="state === MetadataLoadState.Loading" class="row no-gutters">
<div class="col-12">
<div class="card-body">
<h6 class="card-title text-center font-weight-bold text-info" style="font-size: small;"><span class="oi oi-loop-circular"></span> オカズの情報を読み込んでいます</h6>
</div>
</div>
</div>
<div v-else-if="state === MetadataLoadState.Success" class="row no-gutters">
<div v-if="hasImage" class="col-4 justify-content-center align-items-center">
<img :src="metadata.image" alt="Thumbnail" class="w-100 bg-secondary">
</div>
<div :class="descClasses">
<div class="card-body">
<h6 class="card-title font-weight-bold" style="font-size: small;">{{ metadata.title }}</h6>
<template v-if="suggestions.length > 0">
<p class="card-text mb-2" style="font-size: small;">タグ候補<br><span class="text-secondary">(クリックするとタグ入力欄にコピーできます)</span></p>
<ul class="list-inline d-inline">
<li v-for="tag in suggestions"
:class="tagClasses(tag)"
@click="addTag(tag.name)"><span class="oi oi-tag"></span> {{ tag.name }}</li>
</ul>
</template>
</div>
</div>
</div>
<div v-else class="row no-gutters">
<div class="col-12">
<div class="card-body">
<h6 class="card-title text-center font-weight-bold text-danger" style="font-size: small;"><span class="oi oi-circle-x"></span> オカズの情報を読み込めませんでした</h6>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import {Vue, Component, Prop} from "vue-property-decorator";
import {bus, MetadataLoadState} from "../checkin";
type Metadata = {
url: string,
title: string,
description: string,
image: string,
expires_at: string | null,
tags: {
name: string
}[],
};
type Suggestion = {
name: string,
used: boolean,
}
@Component
export default class MetadataPreview extends Vue {
@Prop() readonly state!: MetadataLoadState;
@Prop() readonly metadata!: Metadata | null;
// for use in v-if
private readonly MetadataLoadState = MetadataLoadState;
tags: string[] = [];
created() {
bus.$on("change-tag", (tags: string[]) => this.tags = tags);
bus.$emit("resend-tag");
}
addTag(tag: string) {
bus.$emit("add-tag", tag);
}
tagClasses(s: Suggestion) {
return {
"list-inline-item": true,
"badge": true,
"badge-primary": !s.used,
"badge-secondary": s.used,
"metadata-tag-item": true,
};
}
get suggestions(): Suggestion[] {
if (this.metadata === null) {
return [];
}
return this.metadata.tags.map(t => {
return {
name: t.name,
used: this.tags.indexOf(t.name) !== -1
};
});
}
get hasImage() {
return this.metadata !== null && this.metadata.image !== ''
}
get descClasses() {
return {
"col-8": this.hasImage,
"col-12": !this.hasImage,
};
}
}
</script>
<style lang="scss" scoped>
.link-card-mini {
$height: 150px;
overflow: hidden;
.row > div:first-child {
display: flex;
&:not([display=none]) {
min-height: $height;
img {
position: absolute;
}
}
}
.card-text {
white-space: pre-line;
}
}
.metadata-tag-item {
cursor: pointer;
user-select: none;
}
</style>

View File

@@ -0,0 +1,96 @@
<template>
<div :class="containerClass" @click="$refs.input.focus()">
<input :name="name" type="hidden" :value="tagValue">
<ul class="list-inline d-inline">
<li v-for="(tag, i) in tags"
class="list-inline-item badge badge-primary tag-item"
@click="removeTag(i)"><span class="oi oi-tag"></span> {{ tag }} | x</li>
</ul>
<input :id="id"
ref="input"
type="text"
class="tag-input"
v-model="buffer"
@keydown="onKeyDown">
</div>
</template>
<script lang="ts">
import {Vue, Component, Prop, Watch} from "vue-property-decorator";
import {bus} from "../checkin";
@Component
export default class TagInput extends Vue {
@Prop(String) readonly id!: string;
@Prop(String) readonly name!: string;
@Prop(String) readonly value!: string;
@Prop(Boolean) readonly isInvalid!: boolean;
tags: string[] = this.value.trim() !== "" ? this.value.trim().split(" ") : [];
buffer: string = "";
created() {
bus.$on("add-tag", (tag: string) => this.tags.indexOf(tag) === -1 && this.tags.push(tag));
bus.$on("resend-tag", () => bus.$emit("change-tag", this.tags));
}
onKeyDown(event: KeyboardEvent) {
if (this.buffer.trim() !== "") {
switch (event.key) {
case 'Tab':
case 'Enter':
case ' ':
if ((event as any).isComposing !== true) {
this.tags.push(this.buffer);
this.buffer = "";
}
event.preventDefault();
break;
case 'Unidentified':
// 実際にテキストボックスに入力されている文字を見に行く (フォールバック処理)
if (event.srcElement && (event.srcElement as HTMLInputElement).value.slice(-1) == ' ') {
this.tags.push(this.buffer);
this.buffer = "";
event.preventDefault();
}
break;
}
} else if (event.key === "Enter") {
// 誤爆防止
event.preventDefault();
}
}
removeTag(index: number) {
this.tags.splice(index, 1);
}
@Watch("tags")
onTagsChanged() {
bus.$emit("change-tag", this.tags);
}
get containerClass(): object {
return {
"form-control": true,
"h-auto": true,
"is-invalid": this.isInvalid
};
}
get tagValue(): string {
return this.tags.join(" ");
}
}
</script>
<style lang="scss" scoped>
.tag-item {
cursor: pointer;
}
.tag-input {
border: 0;
outline: 0;
}
</style>

38
resources/assets/js/home.js vendored Normal file
View File

@@ -0,0 +1,38 @@
import Chart from 'chart.js';
const graph = document.getElementById('global-count-graph');
const labels = JSON.parse(document.getElementById('global-count-labels').textContent);
const data = JSON.parse(document.getElementById('global-count-data').textContent);
new Chart(graph.getContext('2d'), {
type: 'bar',
data: {
labels,
datasets: [{
data,
backgroundColor: 'rgba(0, 0, 0, .1)',
borderColor: 'rgba(0, 0, 0, .25)',
borderWidth: 1
}]
},
options: {
maintainAspectRatio: false,
legend: {
display: false
},
elements: {
line: {}
},
scales: {
xAxes: [{
display: false
}],
yAxes: [{
display: false,
ticks: {
beginAtZero: true
}
}]
}
}
});

View File

@@ -0,0 +1,5 @@
$('#protected').on('change', function () {
if (!$(this).prop('checked')) {
alert('チェックイン履歴を公開に切り替えると、個別に非公開設定されているものを除いた全てのチェックインが誰でも閲覧できるようになります。\nご注意ください。');
}
});

Some files were not shown because too many files have changed in this diff Show More