diff --git a/.circleci/config.yml b/.circleci/config.yml index 4307bf0..0c04d93 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -57,6 +57,12 @@ jobs: - store_test_results: path: /tmp/php-cs-fixer + # Run stylelint + - run: + name: stylelint + command: yarn run stylelint + when: always + # Run unit test - run: command: | diff --git a/.gitignore b/.gitignore index 55d3da6..323d21f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ /storage/*.key /vendor /.idea +/.vscode /.vagrant Homestead.json Homestead.yaml diff --git a/Dockerfile b/Dockerfile index 65d49a8..565fba1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,5 @@ +FROM node:10-jessie as node + FROM php:7.1-apache ENV APACHE_DOCUMENT_ROOT /var/www/html/public @@ -15,6 +17,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"] diff --git a/README.md b/README.md index 7953628..8bd1e80 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ a.k.a. shikorism.net ## 構成 - Laravel 5.5 -- Bootstrap 4.2.1 +- Bootstrap 4.3.1 ## 実行環境 @@ -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. 暗号化キーの作成と、データベースのマイグレーションを行います。 @@ -52,7 +53,14 @@ docker-compose exec web php artisan migrate docker-compose exec web chown -R www-data /var/www/html ``` -7. 最後に `.env` を読み込み直すために起動し直します。 +7. アセットをビルドします。 + +``` +docker-compose exec web yarn dev +``` + + +8. 最後に `.env` を読み込み直すために起動し直します。 ``` docker-compose up -d @@ -68,6 +76,16 @@ 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のドキュメントなどを当たってください。 + ## 環境構築上の諸注意 - 初版時点では、DB サーバとして PostgreSQL を使うよう .env ファイルを設定するくらいです。 diff --git a/app/Console/Commands/DemoteUser.php b/app/Console/Commands/DemoteUser.php new file mode 100644 index 0000000..94dfe58 --- /dev/null +++ b/app/Console/Commands/DemoteUser.php @@ -0,0 +1,61 @@ +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.'); + } + } +} diff --git a/app/Console/Commands/PromoteUser.php b/app/Console/Commands/PromoteUser.php new file mode 100644 index 0000000..b737ca5 --- /dev/null +++ b/app/Console/Commands/PromoteUser.php @@ -0,0 +1,61 @@ +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.'); + } + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 622e774..da3f264 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -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'); } } diff --git a/app/Http/Controllers/Admin/DashboardController.php b/app/Http/Controllers/Admin/DashboardController.php new file mode 100644 index 0000000..b09d0c8 --- /dev/null +++ b/app/Http/Controllers/Admin/DashboardController.php @@ -0,0 +1,14 @@ +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', 'お知らせを削除しました。'); + } +} diff --git a/app/Http/Controllers/EjaculationController.php b/app/Http/Controllers/EjaculationController.php index 4490677..d2292e0 100644 --- a/app/Http/Controllers/EjaculationController.php +++ b/app/Http/Controllers/EjaculationController.php @@ -30,9 +30,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', @@ -113,9 +110,6 @@ class EjaculationController extends Controller $ejaculation = Ejaculation::findOrFail($id); $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', diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 93bf68b..95714cf 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -35,6 +35,7 @@ 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' => [ diff --git a/app/Http/Middleware/NormalizeLineEnding.php b/app/Http/Middleware/NormalizeLineEnding.php new file mode 100644 index 0000000..e0ec87e --- /dev/null +++ b/app/Http/Middleware/NormalizeLineEnding.php @@ -0,0 +1,30 @@ +input() as $key => $value) { + $newInput[$key] = str_replace(["\r\n", "\r"], "\n", $value); + } + $request->replace($newInput); + + return $next($request); + } +} diff --git a/app/Http/Requests/AdminInfoStoreRequest.php b/app/Http/Requests/AdminInfoStoreRequest.php new file mode 100644 index 0000000..45b3d2e --- /dev/null +++ b/app/Http/Requests/AdminInfoStoreRequest.php @@ -0,0 +1,35 @@ + ['required', Rule::in(array_keys(Information::CATEGORIES))], + 'pinned' => 'nullable|boolean', + 'title' => 'required|string|max:255', + 'content' => 'required|string|max:10000' + ]; + } +} diff --git a/app/Information.php b/app/Information.php index e0ae6ed..2b237fb 100644 --- a/app/Information.php +++ b/app/Information.php @@ -16,5 +16,9 @@ class Information extends Model 3 => ['label' => 'メンテナンス', 'class' => 'badge-warning'] ]; + protected $fillable = [ + 'category', 'pinned', 'title', 'content' + ]; + protected $dates = ['deleted_at']; } diff --git a/app/MetadataResolver/DLsiteResolver.php b/app/MetadataResolver/DLsiteResolver.php index 99aae49..9f6d6e7 100644 --- a/app/MetadataResolver/DLsiteResolver.php +++ b/app/MetadataResolver/DLsiteResolver.php @@ -26,6 +26,17 @@ class DLsiteResolver implements Resolver $res = $this->client->get($url); if ($res->getStatusCode() === 200) { $metadata = $this->ogpResolver->parse($res->getBody()); + + // 抽出 + preg_match('~\[(.+)\] \| DLsite$~', $metadata->title, $match); + $maker = $match[1]; + + // 余分な文を消す + $metadata->title = trim(preg_replace('~ \[.+\] \| DLsite$~', '', $metadata->title)); + $metadata->description = trim(preg_replace('~「DLsite.+」は同人誌・同人ゲーム・同人音声のダウンロードショップ。お気に入りの作品をすぐダウンロードできてすぐ楽しめる!毎日更新しているのであなたが探している作品にきっと出会えます。国内最大級の二次元総合ダウンロードショップ「DLsite」!$~', '', $metadata->description)); + + // 整形 + $metadata->description = 'サークル: ' . $maker . PHP_EOL . $metadata->description; $metadata->image = str_replace('img_sam.jpg', 'img_main.jpg', $metadata->image); return $metadata; diff --git a/app/MetadataResolver/FC2ContentsResolver.php b/app/MetadataResolver/FC2ContentsResolver.php new file mode 100644 index 0000000..36a3b8b --- /dev/null +++ b/app/MetadataResolver/FC2ContentsResolver.php @@ -0,0 +1,44 @@ +client = $client; + $this->ogpResolver = $ogpResolver; + } + + public function resolve(string $url): Metadata + { + $res = $this->client->get($url); + if ($res->getStatusCode() === 200) { + $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; + } else { + throw new \RuntimeException("{$res->getStatusCode()}: $url"); + } + } +} diff --git a/app/MetadataResolver/FanzaResolver.php b/app/MetadataResolver/FanzaResolver.php index cfb73db..b1e1c66 100644 --- a/app/MetadataResolver/FanzaResolver.php +++ b/app/MetadataResolver/FanzaResolver.php @@ -27,6 +27,7 @@ class FanzaResolver implements Resolver if ($res->getStatusCode() === 200) { $metadata = $this->ogpResolver->parse($res->getBody()); $metadata->image = preg_replace("~(pr|ps)\.jpg$~", 'pl.jpg', $metadata->image); + $metadata->description = str_replace('<>', '', $metadata->description); return $metadata; } else { diff --git a/app/MetadataResolver/MelonbooksResolver.php b/app/MetadataResolver/MelonbooksResolver.php index 5cc3669..b94b719 100644 --- a/app/MetadataResolver/MelonbooksResolver.php +++ b/app/MetadataResolver/MelonbooksResolver.php @@ -30,11 +30,40 @@ class MelonbooksResolver implements Resolver if ($res->getStatusCode() === 200) { $metadata = $this->ogpResolver->parse($res->getBody()); + $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'); + // 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('
', "\n", $specialDescriptionNodelist->item(0)->nodeValue)) . "\n"; + if ($specialDescriptionNodelist->length === 2) { + $description .= "\n"; + $description .= trim(str_replace('
', "\n", $specialDescriptionNodelist->item(1)->nodeValue)) . "\n"; + } + } + + if ($descriptionNodelist->length !== 0) { + $description .= trim(str_replace('
', "\n", $descriptionNodelist->item(0)->nodeValue)); + } + + $metadata->title = $title; + $metadata->description = trim($description); + return $metadata; } else { throw new \RuntimeException("{$res->getStatusCode()}: $url"); diff --git a/app/MetadataResolver/MetadataResolver.php b/app/MetadataResolver/MetadataResolver.php index 5bf195f..181aab3 100644 --- a/app/MetadataResolver/MetadataResolver.php +++ b/app/MetadataResolver/MetadataResolver.php @@ -16,6 +16,7 @@ class MetadataResolver implements Resolver '~ec\.toranoana\.jp/tora_r/ec/item/.*~' => ToranoanaResolver::class, '~iwara\.tv/videos/.*~' => IwaraResolver::class, '~www\.dlsite\.com/.*/work/=/product_id/..\d+\.html~' => DLsiteResolver::class, + '~dlsite\.jp/mawtw/..\d+~' => DLsiteResolver::class, '~www\.pixiv\.net/member_illust\.php\?illust_id=\d+~' => PixivResolver::class, '~fantia\.jp/posts/\d+~' => FantiaResolver::class, '~dmm\.co\.jp/~' => FanzaResolver::class, @@ -23,6 +24,8 @@ class MetadataResolver implements Resolver '~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, ]; public $mimeTypes = [ diff --git a/app/MetadataResolver/PatreonResolver.php b/app/MetadataResolver/PatreonResolver.php index 5ce28e7..7ba3235 100644 --- a/app/MetadataResolver/PatreonResolver.php +++ b/app/MetadataResolver/PatreonResolver.php @@ -28,11 +28,11 @@ class PatreonResolver implements Resolver if ($res->getStatusCode() === 200) { $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; + 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; } else { diff --git a/app/MetadataResolver/PlurkResolver.php b/app/MetadataResolver/PlurkResolver.php new file mode 100644 index 0000000..7422fef --- /dev/null +++ b/app/MetadataResolver/PlurkResolver.php @@ -0,0 +1,44 @@ +client = $client; + $this->ogpResolver = $ogpResolver; + } + + public function resolve(string $url): Metadata + { + $res = $this->client->get($url); + if ($res->getStatusCode() === 200) { + $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; + } else { + throw new \RuntimeException("{$res->getStatusCode()}: $url"); + } + } +} diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index e12ff88..600ff72 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -25,6 +25,8 @@ class AuthServiceProvider extends ServiceProvider { $this->registerPolicies(); - // + Gate::define('admin', function ($user) { + return $user->is_admin; + }); } } diff --git a/database/migrations/2019_02_11_140657_add_is_admin_to_users.php b/database/migrations/2019_02_11_140657_add_is_admin_to_users.php new file mode 100644 index 0000000..2b37986 --- /dev/null +++ b/database/migrations/2019_02_11_140657_add_is_admin_to_users.php @@ -0,0 +1,32 @@ +boolean('is_admin')->default(false); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('is_admin'); + }); + } +} diff --git a/dist/bin/tissue-entrypoint.sh b/dist/bin/tissue-entrypoint.sh index 49280f5..5878f2f 100755 --- a/dist/bin/tissue-entrypoint.sh +++ b/dist/bin/tissue-entrypoint.sh @@ -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 "$@" diff --git a/dist/php.d/99-xdebug.ini b/dist/php.d/99-xdebug.ini index 007daa1..f2a7c9d 100644 --- a/dist/php.d/99-xdebug.ini +++ b/dist/php.d/99-xdebug.ini @@ -1,4 +1,4 @@ ; Dockerでのデバッグ用設定 zend_extension=xdebug.so xdebug.remote_enable=true -xdebug.remote_host=host.docker.internal \ No newline at end of file +xdebug.remote_host=${PHP_XDEBUG_REMOTE_HOST} \ No newline at end of file diff --git a/package.json b/package.json index c43f1b0..01fce83 100644 --- a/package.json +++ b/package.json @@ -7,21 +7,38 @@ "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": { - "bootstrap": "^4.2.1", + "bootstrap": "^4.3.1", "cal-heatmap": "^3.3.10", "chart.js": "^2.7.1", "cross-env": "^5.2.0", + "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", "vue-template-compiler": "^2.6.6" + }, + "stylelint": { + "extends": "stylelint-config-recess-order" + }, + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, + "lint-staged": { + "*.{css,scss}": ["stylelint --fix", "git add"] } } diff --git a/public/dashboard.png b/public/dashboard.png new file mode 100644 index 0000000..c85ca1c Binary files /dev/null and b/public/dashboard.png differ diff --git a/resources/assets/js/app.js b/resources/assets/js/app.js index 0af3853..6af9993 100644 --- a/resources/assets/js/app.js +++ b/resources/assets/js/app.js @@ -21,12 +21,10 @@ $(() => { $('.alert').alert(); $('.tis-page-selector').pageSelector(); - if (document.getElementById('status')) { - setTimeout(function () { - $('#status').alert('close'); - }, 5000); - } - $('.link-card').linkCard(); - $('#deleteCheckinModal').deleteCheckinModal(); + const $deleteCheckinModal = $('#deleteCheckinModal').deleteCheckinModal(); + $(document).on('click', '[data-target="#deleteCheckinModal"]', function (event) { + event.preventDefault(); + $deleteCheckinModal.modal('show', this); + }); }); \ No newline at end of file diff --git a/resources/assets/sass/_bootstrap-custom.scss b/resources/assets/sass/_bootstrap-custom.scss new file mode 100644 index 0000000..bafdd03 --- /dev/null +++ b/resources/assets/sass/_bootstrap-custom.scss @@ -0,0 +1,19 @@ +.card-img-left { + width: 100%; + @include border-left-radius($card-inner-border-radius); +} + +.card-img-right { + width: 100%; + @include border-right-radius($card-inner-border-radius); +} + +.card-img-top-to-left { + width: 100%; + @include media-breakpoint-down(md) { + @include border-top-radius($card-inner-border-radius); + } + @include media-breakpoint-up(lg) { + @include border-left-radius($card-inner-border-radius); + } +} \ No newline at end of file diff --git a/resources/assets/sass/app.scss b/resources/assets/sass/app.scss index 42fa070..e691a70 100644 --- a/resources/assets/sass/app.scss +++ b/resources/assets/sass/app.scss @@ -1,8 +1,15 @@ +// Bootstrap Variable Overlide +$primary: #e53fb1; + // Bootstrap @import "~bootstrap/scss/bootstrap"; +@import "bootstrap-custom"; // Open Iconic @import "~open-iconic/font/css/open-iconic-bootstrap"; // Legacy app styles -@import "tissue.css"; \ No newline at end of file +@import "tissue.css"; + +// Components +@import "components/link-card"; \ No newline at end of file diff --git a/resources/assets/sass/components/_link-card.scss b/resources/assets/sass/components/_link-card.scss new file mode 100644 index 0000000..846fde9 --- /dev/null +++ b/resources/assets/sass/components/_link-card.scss @@ -0,0 +1,21 @@ +.link-card { + .row > div:last-child { + max-height: 400px; + overflow: hidden; + + // 省略を表す影を付けるやつ + &::before { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + content: ''; + background: linear-gradient(transparent 320px, white); + } + } + + .card-text { + white-space: pre-line; + } +} \ No newline at end of file diff --git a/resources/assets/sass/tissue.css b/resources/assets/sass/tissue.css index f7dfd0c..2dd99e2 100644 --- a/resources/assets/sass/tissue.css +++ b/resources/assets/sass/tissue.css @@ -1,14 +1,10 @@ @charset "UTF-8"; .tis-footer { - color: #a2a2a2; font-size: small; - border-top: 1px solid #eee; - background: linear-gradient(to bottom, #f8f9fa, #fff) -} - -.tis-word-wrap { - word-wrap: break-word; + color: #a2a2a2; + background: linear-gradient(to bottom, #f8f9fa, #fff); + border-top: 1px solid #eee } .tis-contribution-graph { @@ -16,8 +12,8 @@ } .tis-need-agecheck .container { - filter: blur(45px); pointer-events: none; + filter: blur(45px); } .container { @@ -25,8 +21,8 @@ } .list-group-item.no-side-border { - border-left: none; border-right: none; + border-left: none; border-radius: 0; } @@ -35,8 +31,8 @@ } .list-group-item.border-bottom-only { - border-left: none; border-right: none; + border-left: none; border-radius: 0; } @@ -54,12 +50,12 @@ } .tis-page-selector { - margin-left: -1px; width: calc(100% + 2px); height: 100%; + margin-left: -1px; + line-height: 1.25; border: 1px solid #dee2e6; border-radius: 0; - line-height: 1.25; } @media (min-width: 992px) { @@ -69,13 +65,13 @@ } #navbarNav > .d-lg-none > .row > div:first-of-type { - padding-left: 15px; padding-right: 7.5px; + padding-left: 15px; } #navbarNav > .d-lg-none > .row > div { - padding-left: 7.5px; padding-right: 15px; + padding-left: 7.5px; } #navbarNav > .d-lg-none > .row > .col .btn { @@ -84,16 +80,4 @@ #navbarAccountDropdownSp { max-width: calc(100vw - 5em); -} - -.card-img-left { - width: 100%; - border-top-left-radius: calc(.25rem - 1px); - border-bottom-left-radius: calc(.25rem - 1px); -} - -.card-img-right { - width: 100%; - border-top-right-radius: calc(.25rem - 1px); - border-bottom-right-radius: calc(.25rem - 1px); } \ No newline at end of file diff --git a/resources/lang/ja/validation.php b/resources/lang/ja/validation.php index ecf3a03..d154816 100644 --- a/resources/lang/ja/validation.php +++ b/resources/lang/ja/validation.php @@ -119,6 +119,8 @@ return [ 'attributes' => [ 'email' => 'メールアドレス', 'password' => 'パスワード', + 'title' => 'タイトル', + 'content' => '本文', ], ]; diff --git a/resources/views/admin/dashboard.blade.php b/resources/views/admin/dashboard.blade.php new file mode 100644 index 0000000..beb9602 --- /dev/null +++ b/resources/views/admin/dashboard.blade.php @@ -0,0 +1,10 @@ +@extends('layouts.admin') + +@section('title', 'ダッシュボード') + +@section('tab-content') +
+ +

TODO: 役に立つ情報を表示する

+
+@endsection \ No newline at end of file diff --git a/resources/views/admin/info/create.blade.php b/resources/views/admin/info/create.blade.php new file mode 100644 index 0000000..576b7ab --- /dev/null +++ b/resources/views/admin/info/create.blade.php @@ -0,0 +1,54 @@ +@extends('layouts.admin') + +@section('title', 'お知らせ') + +@section('tab-content') +
+

お知らせの作成

+
+
+ {{ csrf_field() }} + +
+
+ + +
+
+
+ + +
+
+
+
+ + + + @if ($errors->has('title')) +
{{ $errors->first('title') }}
+ @endif +
+
+ + + + 最大 10000 文字、Markdown 形式 + + + @if ($errors->has('content')) +
{{ $errors->first('content') }}
+ @endif +
+ +
+ +
+
+
+@endsection \ No newline at end of file diff --git a/resources/views/admin/info/edit.blade.php b/resources/views/admin/info/edit.blade.php new file mode 100644 index 0000000..d5eb513 --- /dev/null +++ b/resources/views/admin/info/edit.blade.php @@ -0,0 +1,60 @@ +@extends('layouts.admin') + +@section('title', 'お知らせ') + +@section('tab-content') +
+

お知らせの編集

+
+
+ {{ method_field('PUT') }} + {{ csrf_field() }} + +
+
+ + +
+
+
+ pinned) ? 'checked' : ''}}> + +
+
+
+
+ + + + @if ($errors->has('title')) +
{{ $errors->first('title') }}
+ @endif +
+
+ + + + 最大 10000 文字、Markdown 形式 + + + @if ($errors->has('content')) +
{{ $errors->first('content') }}
+ @endif +
+ +
+ + +
+
+
+ {{ method_field('DELETE') }} + {{ csrf_field() }} +
+
+@endsection \ No newline at end of file diff --git a/resources/views/admin/info/index.blade.php b/resources/views/admin/info/index.blade.php new file mode 100644 index 0000000..8d5ae40 --- /dev/null +++ b/resources/views/admin/info/index.blade.php @@ -0,0 +1,57 @@ +@extends('layouts.admin') + +@section('title', 'お知らせ') + +@section('tab-content') +
+

お知らせ

+
+
+ 新規作成 +
+ + + + + + + + + + @foreach($informations as $info) + + + + + + @endforeach + +
カテゴリタイトル作成日
+ @if ($info->pinned) + ピン留め + @endif + {{ $categories[$info->category]['label'] }} + + {{ $info->title }} + + {{ $info->created_at->format('Y年n月j日') }} +
+ +
+@endsection \ No newline at end of file diff --git a/resources/views/components/link-card.blade.php b/resources/views/components/link-card.blade.php index 5b99fd4..94c17f9 100644 --- a/resources/views/components/link-card.blade.php +++ b/resources/views/components/link-card.blade.php @@ -2,7 +2,7 @@
- Thumbnail + Thumbnail
@@ -63,7 +63,7 @@ @endif @if (!empty($ejaculation->note)) -

+

{!! Formatter::linkify(nl2br(e($ejaculation->note))) !!}

@endif diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php index 9bf2128..34128ce 100644 --- a/resources/views/home.blade.php +++ b/resources/views/home.blade.php @@ -54,7 +54,7 @@

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

@endif @@ -104,5 +104,6 @@ @push('script') + -@endpush \ No newline at end of file +@endpush diff --git a/resources/views/layouts/admin.blade.php b/resources/views/layouts/admin.blade.php new file mode 100644 index 0000000..45fa2b5 --- /dev/null +++ b/resources/views/layouts/admin.blade.php @@ -0,0 +1,20 @@ +@extends('layouts.base') + +@section('content') +
+
+ +
+ @yield('tab-content') +
+
+
+@endsection \ No newline at end of file diff --git a/resources/views/layouts/base.blade.php b/resources/views/layouts/base.blade.php index 3947745..38ec46e 100644 --- a/resources/views/layouts/base.blade.php +++ b/resources/views/layouts/base.blade.php @@ -89,7 +89,7 @@
- チェックイン + チェックイン