commit
5153de54d2
@ -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: |
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -8,6 +8,7 @@
|
||||
/storage/*.key
|
||||
/vendor
|
||||
/.idea
|
||||
/.vscode
|
||||
/.vagrant
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
|
12
Dockerfile
12
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"]
|
||||
|
||||
|
24
README.md
24
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 ファイルを設定するくらいです。
|
||||
|
61
app/Console/Commands/DemoteUser.php
Normal file
61
app/Console/Commands/DemoteUser.php
Normal 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.');
|
||||
}
|
||||
}
|
||||
}
|
61
app/Console/Commands/PromoteUser.php
Normal file
61
app/Console/Commands/PromoteUser.php
Normal 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.');
|
||||
}
|
||||
}
|
||||
}
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
14
app/Http/Controllers/Admin/DashboardController.php
Normal file
14
app/Http/Controllers/Admin/DashboardController.php
Normal 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');
|
||||
}
|
||||
}
|
75
app/Http/Controllers/Admin/InfoController.php
Normal file
75
app/Http/Controllers/Admin/InfoController.php
Normal 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', 'お知らせを削除しました。');
|
||||
}
|
||||
}
|
@ -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',
|
||||
|
@ -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' => [
|
||||
|
30
app/Http/Middleware/NormalizeLineEnding.php
Normal file
30
app/Http/Middleware/NormalizeLineEnding.php
Normal 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);
|
||||
}
|
||||
}
|
35
app/Http/Requests/AdminInfoStoreRequest.php
Normal file
35
app/Http/Requests/AdminInfoStoreRequest.php
Normal 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'
|
||||
];
|
||||
}
|
||||
}
|
@ -16,5 +16,9 @@ class Information extends Model
|
||||
3 => ['label' => 'メンテナンス', 'class' => 'badge-warning']
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'category', 'pinned', 'title', 'content'
|
||||
];
|
||||
|
||||
protected $dates = ['deleted_at'];
|
||||
}
|
||||
|
@ -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;
|
||||
|
44
app/MetadataResolver/FC2ContentsResolver.php
Normal file
44
app/MetadataResolver/FC2ContentsResolver.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?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);
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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('<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;
|
||||
} else {
|
||||
throw new \RuntimeException("{$res->getStatusCode()}: $url");
|
||||
|
@ -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 = [
|
||||
|
@ -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 {
|
||||
|
44
app/MetadataResolver/PlurkResolver.php
Normal file
44
app/MetadataResolver/PlurkResolver.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?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);
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
@ -25,6 +25,8 @@ class AuthServiceProvider extends ServiceProvider
|
||||
{
|
||||
$this->registerPolicies();
|
||||
|
||||
//
|
||||
Gate::define('admin', function ($user) {
|
||||
return $user->is_admin;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
});
|
||||
}
|
||||
}
|
9
dist/bin/tissue-entrypoint.sh
vendored
9
dist/bin/tissue-entrypoint.sh
vendored
@ -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 "$@"
|
||||
|
2
dist/php.d/99-xdebug.ini
vendored
2
dist/php.d/99-xdebug.ini
vendored
@ -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}
|
21
package.json
21
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"]
|
||||
}
|
||||
}
|
||||
|
BIN
public/dashboard.png
Normal file
BIN
public/dashboard.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 50 KiB |
12
resources/assets/js/app.js
vendored
12
resources/assets/js/app.js
vendored
@ -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);
|
||||
});
|
||||
});
|
19
resources/assets/sass/_bootstrap-custom.scss
vendored
Normal file
19
resources/assets/sass/_bootstrap-custom.scss
vendored
Normal file
@ -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);
|
||||
}
|
||||
}
|
9
resources/assets/sass/app.scss
vendored
9
resources/assets/sass/app.scss
vendored
@ -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";
|
||||
@import "tissue.css";
|
||||
|
||||
// Components
|
||||
@import "components/link-card";
|
21
resources/assets/sass/components/_link-card.scss
vendored
Normal file
21
resources/assets/sass/components/_link-card.scss
vendored
Normal file
@ -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;
|
||||
}
|
||||
}
|
36
resources/assets/sass/tissue.css
vendored
36
resources/assets/sass/tissue.css
vendored
@ -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);
|
||||
}
|
@ -119,6 +119,8 @@ return [
|
||||
'attributes' => [
|
||||
'email' => 'メールアドレス',
|
||||
'password' => 'パスワード',
|
||||
'title' => 'タイトル',
|
||||
'content' => '本文',
|
||||
],
|
||||
|
||||
];
|
||||
|
10
resources/views/admin/dashboard.blade.php
Normal file
10
resources/views/admin/dashboard.blade.php
Normal file
@ -0,0 +1,10 @@
|
||||
@extends('layouts.admin')
|
||||
|
||||
@section('title', 'ダッシュボード')
|
||||
|
||||
@section('tab-content')
|
||||
<div class="container d-flex flex-column align-items-center">
|
||||
<img src="{{ asset('dashboard.png') }}" class="w-50"/>
|
||||
<p class="text-muted">TODO: 役に立つ情報を表示する</p>
|
||||
</div>
|
||||
@endsection
|
54
resources/views/admin/info/create.blade.php
Normal file
54
resources/views/admin/info/create.blade.php
Normal file
@ -0,0 +1,54 @@
|
||||
@extends('layouts.admin')
|
||||
|
||||
@section('title', 'お知らせ')
|
||||
|
||||
@section('tab-content')
|
||||
<div class="container">
|
||||
<h2>お知らせの作成</h2>
|
||||
<hr>
|
||||
<form action="{{ route('admin.info.store') }}" method="post">
|
||||
{{ csrf_field() }}
|
||||
|
||||
<div class="row">
|
||||
<div class="form-group col-12 col-lg-6">
|
||||
<label for="category">カテゴリ</label>
|
||||
<select id="category" name="category" class="form-control">
|
||||
@foreach($categories as $id => $category)
|
||||
<option value="{{ $id }}" {{ old('category') == $id ? 'selected' : '' }}>{{ $category['label'] }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group col-12 col-lg-6 d-flex flex-column justify-content-center">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input id="pinned" name="pinned" type="checkbox" class="custom-control-input" value="1"
|
||||
{{ old('pinned') ? 'checked' : ''}}>
|
||||
<label for="pinned" class="custom-control-label">ピン留め (常に優先表示) する</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="title">タイトル</label>
|
||||
<input id="title" name="title" type="text" class="form-control {{ $errors->has('title') ? ' is-invalid' : '' }}" value="{{ old('title') }}">
|
||||
|
||||
@if ($errors->has('title'))
|
||||
<div class="invalid-feedback">{{ $errors->first('title') }}</div>
|
||||
@endif
|
||||
</div>
|
||||
<div class="form-group mt-3">
|
||||
<label for="content">本文</label>
|
||||
<textarea id="content" name="content" rows="15" class="form-control {{ $errors->has('content') ? ' is-invalid' : '' }}" maxlength="10000">{{ old('content') }}</textarea>
|
||||
<small class="form-text text-muted">
|
||||
最大 10000 文字、Markdown 形式
|
||||
</small>
|
||||
|
||||
@if ($errors->has('content'))
|
||||
<div class="invalid-feedback">{{ $errors->first('content') }}</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between">
|
||||
<button type="submit" class="btn btn-primary">登録</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@endsection
|
60
resources/views/admin/info/edit.blade.php
Normal file
60
resources/views/admin/info/edit.blade.php
Normal file
@ -0,0 +1,60 @@
|
||||
@extends('layouts.admin')
|
||||
|
||||
@section('title', 'お知らせ')
|
||||
|
||||
@section('tab-content')
|
||||
<div class="container">
|
||||
<h2>お知らせの編集</h2>
|
||||
<hr>
|
||||
<form action="{{ route('admin.info.update', ['info' => $info]) }}" method="post">
|
||||
{{ method_field('PUT') }}
|
||||
{{ csrf_field() }}
|
||||
|
||||
<div class="row">
|
||||
<div class="form-group col-12 col-lg-6">
|
||||
<label for="category">カテゴリ</label>
|
||||
<select id="category" name="category" class="form-control">
|
||||
@foreach($categories as $id => $category)
|
||||
<option value="{{ $id }}" {{ (old('category') ?? $info->category) == $id ? 'selected' : '' }}>{{ $category['label'] }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group col-12 col-lg-6 d-flex flex-column justify-content-center">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input id="pinned" name="pinned" type="checkbox" class="custom-control-input" value="1"
|
||||
{{ (is_bool(old('pinned')) ? old('pinned') : $info->pinned) ? 'checked' : ''}}>
|
||||
<label for="pinned" class="custom-control-label">ピン留め (常に優先表示) する</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="title">タイトル</label>
|
||||
<input id="title" name="title" type="text" class="form-control {{ $errors->has('title') ? ' is-invalid' : '' }}" value="{{ old('title') ?? $info->title }}">
|
||||
|
||||
@if ($errors->has('title'))
|
||||
<div class="invalid-feedback">{{ $errors->first('title') }}</div>
|
||||
@endif
|
||||
</div>
|
||||
<div class="form-group mt-3">
|
||||
<label for="content">本文</label>
|
||||
<textarea id="content" name="content" rows="15" class="form-control {{ $errors->has('content') ? ' is-invalid' : '' }}" maxlength="10000">{{ old('content') ?? $info->content }}</textarea>
|
||||
<small class="form-text text-muted">
|
||||
最大 10000 文字、Markdown 形式
|
||||
</small>
|
||||
|
||||
@if ($errors->has('content'))
|
||||
<div class="invalid-feedback">{{ $errors->first('content') }}</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between">
|
||||
<button type="submit" class="btn btn-primary">更新</button>
|
||||
<button type="submit" class="btn btn-danger" form="delete-form">削除</button>
|
||||
</div>
|
||||
</form>
|
||||
<form id="delete-form" action="{{ route('admin.info.destroy', ['info' => $info]) }}" method="post">
|
||||
{{ method_field('DELETE') }}
|
||||
{{ csrf_field() }}
|
||||
</form>
|
||||
</div>
|
||||
@endsection
|
57
resources/views/admin/info/index.blade.php
Normal file
57
resources/views/admin/info/index.blade.php
Normal file
@ -0,0 +1,57 @@
|
||||
@extends('layouts.admin')
|
||||
|
||||
@section('title', 'お知らせ')
|
||||
|
||||
@section('tab-content')
|
||||
<div class="container">
|
||||
<h2>お知らせ</h2>
|
||||
<hr>
|
||||
<div class="d-flex mb-3">
|
||||
<a href="{{ route('admin.info.create') }}" class="btn btn-primary">新規作成</a>
|
||||
</div>
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>カテゴリ</th>
|
||||
<th>タイトル</th>
|
||||
<th>作成日</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($informations as $info)
|
||||
<tr>
|
||||
<td>
|
||||
@if ($info->pinned)
|
||||
<span class="badge badge-secondary"><span class="oi oi-pin"></span>ピン留め</span>
|
||||
@endif
|
||||
<span class="badge {{ $categories[$info->category]['class'] }}">{{ $categories[$info->category]['label'] }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ route('admin.info.edit', ['id' => $info->id]) }}">{{ $info->title }}</a>
|
||||
</td>
|
||||
<td>
|
||||
{{ $info->created_at->format('Y年n月j日') }}
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
<ul class="pagination mt-4 justify-content-center">
|
||||
<li class="page-item {{ $informations->currentPage() === 1 ? 'disabled' : '' }}">
|
||||
<a class="page-link" href="{{ $informations->previousPageUrl() }}" aria-label="Previous">
|
||||
<span aria-hidden="true">«</span>
|
||||
<span class="sr-only">Previous</span>
|
||||
</a>
|
||||
</li>
|
||||
@for ($i = 1; $i <= $informations->lastPage(); $i++)
|
||||
<li class="page-item {{ $i === $informations->currentPage() ? 'active' : '' }}"><a href="{{ $informations->url($i) }}" class="page-link">{{ $i }}</a></li>
|
||||
@endfor
|
||||
<li class="page-item {{ $informations->currentPage() === $informations->lastPage() ? 'disabled' : '' }}">
|
||||
<a class="page-link" href="{{ $informations->nextPageUrl() }}" aria-label="Next">
|
||||
<span aria-hidden="true">»</span>
|
||||
<span class="sr-only">Next</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@endsection
|
@ -2,7 +2,7 @@
|
||||
<a class="text-dark card-link" href="{{ $link }}" target="_blank" rel="noopener">
|
||||
<div class="row no-gutters">
|
||||
<div class="col-12 col-md-6">
|
||||
<img src="" alt="Thumbnail" class="card-img-left bg-secondary">
|
||||
<img src="" alt="Thumbnail" class="card-img-top-to-left bg-secondary">
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="card-body">
|
||||
|
@ -36,7 +36,7 @@
|
||||
<a class="text-secondary timeline-action-item" href="{{ route('checkin', ['link' => $ejaculation->link, 'tags' => $ejaculation->textTags()]) }}"><span class="oi oi-reload" data-toggle="tooltip" data-placement="bottom" title="同じオカズでチェックイン"></span></a>
|
||||
@if ($user->isMe())
|
||||
<a class="text-secondary timeline-action-item" href="{{ route('checkin.edit', ['id' => $ejaculation->id]) }}"><span class="oi oi-pencil" data-toggle="tooltip" data-placement="bottom" title="修正"></span></a>
|
||||
<a class="text-secondary timeline-action-item" href="#" data-toggle="modal" data-target="#deleteCheckinModal" data-id="{{ $ejaculation->id }}" data-date="{{ $ejaculation->ejaculated_date }}"><span class="oi oi-trash" data-toggle="tooltip" data-placement="bottom" title="削除"></span></a>
|
||||
<a class="text-secondary timeline-action-item" href="#" data-target="#deleteCheckinModal" data-id="{{ $ejaculation->id }}" data-date="{{ $ejaculation->ejaculated_date }}"><span class="oi oi-trash" data-toggle="tooltip" data-placement="bottom" title="削除"></span></a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@ -63,7 +63,7 @@
|
||||
@endif
|
||||
<!-- note -->
|
||||
@if (!empty($ejaculation->note))
|
||||
<p class="mb-0 tis-word-wrap">
|
||||
<p class="mb-0 text-break">
|
||||
{!! Formatter::linkify(nl2br(e($ejaculation->note))) !!}
|
||||
</p>
|
||||
@endif
|
||||
|
@ -54,7 +54,7 @@
|
||||
<p class="text-secondary">最近の公開チェックインから、オカズリンク付きのものを表示しています。</p>
|
||||
<ul class="list-group">
|
||||
@foreach ($publicLinkedEjaculations as $ejaculation)
|
||||
<li class="list-group-item no-side-border pt-3 pb-3 tis-word-wrap">
|
||||
<li class="list-group-item no-side-border pt-3 pb-3 text-break">
|
||||
<!-- span -->
|
||||
<div class="d-flex justify-content-between">
|
||||
<h5>
|
||||
@ -85,14 +85,14 @@
|
||||
@endif
|
||||
<!-- note -->
|
||||
@if (!empty($ejaculation->note))
|
||||
<p class="mb-0 tis-word-wrap">
|
||||
<p class="mb-0 text-break">
|
||||
{!! Formatter::linkify(nl2br(e($ejaculation->note))) !!}
|
||||
</p>
|
||||
@endif
|
||||
</li>
|
||||
@endforeach
|
||||
<li class="list-group-item no-side-border text-right">
|
||||
<a href="{{ route('timeline.public') }}">もっと見る »</a>
|
||||
<a href="{{ route('timeline.public') }}" class="stretched-link">もっと見る »</a>
|
||||
</li>
|
||||
</ul>
|
||||
@endif
|
||||
@ -104,5 +104,6 @@
|
||||
@push('script')
|
||||
<script id="global-count-labels" type="application/json">@json(array_keys($globalEjaculationCounts))</script>
|
||||
<script id="global-count-data" type="application/json">@json(array_values($globalEjaculationCounts))</script>
|
||||
<script src="{{ mix('js/vendor/chart.js') }}"></script>
|
||||
<script src="{{ mix('js/home.js') }}"></script>
|
||||
@endpush
|
||||
@endpush
|
||||
|
20
resources/views/layouts/admin.blade.php
Normal file
20
resources/views/layouts/admin.blade.php
Normal file
@ -0,0 +1,20 @@
|
||||
@extends('layouts.base')
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-3">
|
||||
<div class="list-group mb-4">
|
||||
<div class="list-group-item disabled font-weight-bold">管理</div>
|
||||
<a class="list-group-item list-group-item-action {{ Route::is('admin.dashboard') ? 'active' : '' }}"
|
||||
href="{{ route('admin.dashboard') }}"><span class="oi oi-dashboard mr-1"></span> ダッシュボード</a>
|
||||
<a class="list-group-item list-group-item-action {{ Route::is('admin.info*') ? 'active' : '' }}"
|
||||
href="{{ route('admin.info') }}"><span class="oi oi-bullhorn mr-1"></span> お知らせ</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-content col-lg-9">
|
||||
@yield('tab-content')
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
@ -89,7 +89,7 @@
|
||||
</div>
|
||||
</form>
|
||||
<form class="form-inline mr-2">
|
||||
<a href="{{ route('checkin') }}" class="btn btn-outline-success">チェックイン</a>
|
||||
<a href="{{ route('checkin') }}" class="btn btn-outline-primary">チェックイン</a>
|
||||
</form>
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item dropdown">
|
||||
@ -105,6 +105,9 @@
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a href="{{ route('setting') }}" class="dropdown-item">設定</a>
|
||||
@can ('admin')
|
||||
<a href="{{ route('admin.dashboard') }}" class="dropdown-item">管理</a>
|
||||
@endcan
|
||||
<a href="{{ route('logout') }}" class="dropdown-item" onclick="event.preventDefault(); document.getElementById('logout-form').submit();">ログアウト</a>
|
||||
</div>
|
||||
</li>
|
||||
@ -114,18 +117,18 @@
|
||||
<div class="d-lg-none">
|
||||
<div class="row mt-2">
|
||||
<div class="col">
|
||||
<a class="btn btn-outline-{{ stripos(Route::currentRouteName(), 'home') === 0 ? 'primary' : 'secondary'}}" href="{{ route('home') }}" role="button">ホーム</a>
|
||||
<a class="btn btn-{{ stripos(Route::currentRouteName(), 'home') === 0 ? 'primary' : 'outline-secondary'}}" href="{{ route('home') }}" role="button">ホーム</a>
|
||||
</div>
|
||||
<div class="col">
|
||||
<a class="btn btn-outline-{{ stripos(Route::currentRouteName(), 'user.profile') === 0 ? 'primary' : 'secondary'}}" href="{{ route('user.profile', ['name' => Auth::user()->name]) }}" role="button">タイムライン</a>
|
||||
<a class="btn btn-{{ stripos(Route::currentRouteName(), 'user.profile') === 0 ? 'primary' : 'outline-secondary'}}" href="{{ route('user.profile', ['name' => Auth::user()->name]) }}" role="button">タイムライン</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-2">
|
||||
<div class="col">
|
||||
<a class="btn btn-outline-{{ stripos(Route::currentRouteName(), 'user.stats') === 0 ? 'primary' : 'secondary'}}" href="{{ route('user.stats', ['name' => Auth::user()->name]) }}" role="button">グラフ</a>
|
||||
<a class="btn btn-{{ stripos(Route::currentRouteName(), 'user.stats') === 0 ? 'primary' : 'outline-secondary'}}" href="{{ route('user.stats', ['name' => Auth::user()->name]) }}" role="button">グラフ</a>
|
||||
</div>
|
||||
<div class="col">
|
||||
<a class="btn btn-outline-{{ stripos(Route::currentRouteName(), 'user.okazu') === 0 ? 'primary' : 'secondary'}}" href="{{ route('user.okazu', ['name' => Auth::user()->name]) }}" role="button">オカズ</a>
|
||||
<a class="btn btn-{{ stripos(Route::currentRouteName(), 'user.okazu') === 0 ? 'primary' : 'outline-secondary'}}" href="{{ route('user.okazu', ['name' => Auth::user()->name]) }}" role="button">オカズ</a>
|
||||
</div>
|
||||
</div>
|
||||
{{-- <div class="row mt-2">
|
||||
@ -145,7 +148,7 @@
|
||||
</div>
|
||||
<div class="row mt-2">
|
||||
<form class="form-inline col">
|
||||
<a class="btn btn-outline-success" href="{{ route('checkin') }}">チェックイン</a>
|
||||
<a class="btn btn-outline-primary" href="{{ route('checkin') }}">チェックイン</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@ -217,7 +220,9 @@
|
||||
</div>
|
||||
</div>
|
||||
@endguest
|
||||
<script src="{{ mix('js/manifest.js') }}"></script>
|
||||
<script src="{{ mix('js/vendor.js') }}"></script>
|
||||
<script src="{{ mix('js/app.js') }}"></script>
|
||||
@stack('script')
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
@ -6,7 +6,7 @@
|
||||
@else
|
||||
<ul class="list-group">
|
||||
@foreach($results as $ejaculation)
|
||||
<li class="list-group-item border-bottom-only pt-3 pb-3 tis-word-wrap">
|
||||
<li class="list-group-item border-bottom-only pt-3 pb-3 text-break">
|
||||
<!-- span -->
|
||||
<div class="d-flex justify-content-between">
|
||||
<h5>
|
||||
@ -15,10 +15,6 @@
|
||||
</h5>
|
||||
<div>
|
||||
<a class="text-secondary timeline-action-item" href="{{ route('checkin', ['link' => $ejaculation->link, 'tags' => $ejaculation->textTags()]) }}"><span class="oi oi-reload" data-toggle="tooltip" data-placement="bottom" title="同じオカズでチェックイン"></span></a>
|
||||
@if ($ejaculation->user->isMe())
|
||||
<a class="text-secondary timeline-action-item" href="{{ route('checkin.edit', ['id' => $ejaculation->id]) }}"><span class="oi oi-pencil" data-toggle="tooltip" data-placement="bottom" title="修正"></span></a>
|
||||
<a class="text-secondary timeline-action-item" href="#" data-toggle="modal" data-target="#deleteCheckinModal" data-id="{{ $ejaculation->id }}" data-date="{{ $ejaculation->ejaculated_date }}"><span class="oi oi-trash" data-toggle="tooltip" data-placement="bottom" title="削除"></span></a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<!-- tags -->
|
||||
@ -44,7 +40,7 @@
|
||||
@endif
|
||||
<!-- note -->
|
||||
@if (!empty($ejaculation->note))
|
||||
<p class="mb-0 tis-word-wrap">
|
||||
<p class="mb-0 text-break">
|
||||
{!! Formatter::linkify(nl2br(e($ejaculation->note))) !!}
|
||||
</p>
|
||||
@endif
|
||||
|
@ -10,7 +10,7 @@
|
||||
<div class="container-fluid">
|
||||
<div class="row mx-1">
|
||||
@foreach($ejaculations as $ejaculation)
|
||||
<div class="col-12 col-lg-6 col-xl-4 py-3 tis-word-wrap border-top">
|
||||
<div class="col-12 col-lg-6 col-xl-4 py-3 text-break border-top">
|
||||
<!-- span -->
|
||||
<div class="d-flex justify-content-between">
|
||||
<h5>
|
||||
@ -44,7 +44,7 @@
|
||||
@endif
|
||||
<!-- note -->
|
||||
@if (!empty($ejaculation->note))
|
||||
<p class="mb-0 tis-word-wrap">
|
||||
<p class="mb-0 text-break">
|
||||
{!! Formatter::linkify(nl2br(e($ejaculation->note))) !!}
|
||||
</p>
|
||||
@endif
|
||||
|
@ -34,7 +34,7 @@
|
||||
@else
|
||||
<ul class="list-group">
|
||||
@forelse ($ejaculations as $ejaculation)
|
||||
<li class="list-group-item border-bottom-only pt-3 pb-3 tis-word-wrap">
|
||||
<li class="list-group-item border-bottom-only pt-3 pb-3 text-break">
|
||||
<!-- span -->
|
||||
<div class="d-flex justify-content-between">
|
||||
<h5>{{ $ejaculation->ejaculated_span ?? '精通' }} <a href="{{ route('checkin.show', ['id' => $ejaculation->id]) }}" class="text-muted"><small>{{ $ejaculation->before_date }}{{ !empty($ejaculation->before_date) ? ' ~ ' : '' }}{{ $ejaculation->ejaculated_date->format('Y/m/d H:i') }}</small></a></h5>
|
||||
@ -42,7 +42,7 @@
|
||||
<a class="text-secondary timeline-action-item" href="{{ route('checkin', ['link' => $ejaculation->link, 'tags' => $ejaculation->textTags()]) }}"><span class="oi oi-reload" data-toggle="tooltip" data-placement="bottom" title="同じオカズでチェックイン"></span></a>
|
||||
@if ($user->isMe())
|
||||
<a class="text-secondary timeline-action-item" href="{{ route('checkin.edit', ['id' => $ejaculation->id]) }}"><span class="oi oi-pencil" data-toggle="tooltip" data-placement="bottom" title="修正"></span></a>
|
||||
<a class="text-secondary timeline-action-item" href="#" data-toggle="modal" data-target="#deleteCheckinModal" data-id="{{ $ejaculation->id }}" data-date="{{ $ejaculation->ejaculated_date }}"><span class="oi oi-trash" data-toggle="tooltip" data-placement="bottom" title="削除"></span></a>
|
||||
<a class="text-secondary timeline-action-item" href="#" data-target="#deleteCheckinModal" data-id="{{ $ejaculation->id }}" data-date="{{ $ejaculation->ejaculated_date }}"><span class="oi oi-trash" data-toggle="tooltip" data-placement="bottom" title="削除"></span></a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@ -69,7 +69,7 @@
|
||||
@endif
|
||||
<!-- note -->
|
||||
@if (!empty($ejaculation->note))
|
||||
<p class="mb-0 tis-word-wrap">
|
||||
<p class="mb-0 text-break">
|
||||
{!! Formatter::linkify(nl2br(e($ejaculation->note))) !!}
|
||||
</p>
|
||||
@endif
|
||||
|
@ -30,6 +30,7 @@
|
||||
@endsection
|
||||
|
||||
@push('script')
|
||||
<script id="graph-data" type="application/javascript">@json($graphData)</script>
|
||||
<script id="graph-data" type="application/json">@json($graphData)</script>
|
||||
<script src="{{ mix('js/vendor/chart.js') }}"></script>
|
||||
<script src="{{ mix('js/user/stats.js') }}"></script>
|
||||
@endpush
|
||||
@endpush
|
||||
|
@ -44,3 +44,17 @@ Route::get('/info/{id}', 'InfoController@show')->where('id', '[0-9]+')->name('in
|
||||
Route::redirect('/search', '/search/checkin', 301);
|
||||
Route::get('/search/checkin', 'SearchController@index')->name('search');
|
||||
Route::get('/search/related-tag', 'SearchController@relatedTag')->name('search.related-tag');
|
||||
|
||||
Route::middleware('can:admin')
|
||||
->namespace('Admin')
|
||||
->prefix('admin')
|
||||
->name('admin.')
|
||||
->group(function () {
|
||||
Route::get('/', 'DashboardController@index')->name('dashboard');
|
||||
Route::get('/info', 'InfoController@index')->name('info');
|
||||
Route::get('/info/create', 'InfoController@create')->name('info.create');
|
||||
Route::post('/info', 'InfoController@store')->name('info.store');
|
||||
Route::get('/info/{info}', 'InfoController@edit')->name('info.edit');
|
||||
Route::put('/info/{info}', 'InfoController@update')->name('info.update');
|
||||
Route::delete('/info/{info}', 'InfoController@destroy')->name('info.destroy');
|
||||
});
|
||||
|
80
tests/Unit/Http/Middleware/NormalizeLineEndingTest.php
Normal file
80
tests/Unit/Http/Middleware/NormalizeLineEndingTest.php
Normal file
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Http\Middleware;
|
||||
|
||||
use App\Http\Middleware\NormalizeLineEnding;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Foundation\Testing\WithFaker;
|
||||
use Illuminate\Http\Request;
|
||||
use Tests\TestCase;
|
||||
|
||||
class NormalizeLineEndingTest extends TestCase
|
||||
{
|
||||
public function testCRLFtoLF()
|
||||
{
|
||||
$request = Request::create('/');
|
||||
$request->replace([
|
||||
'test' => "foo\r\nbar"
|
||||
]);
|
||||
|
||||
$middleware = new NormalizeLineEnding();
|
||||
|
||||
$middleware->handle($request, function (Request $request) {
|
||||
$this->assertEquals("foo\nbar", $request->input('test'));
|
||||
});
|
||||
}
|
||||
|
||||
public function testCRtoLF()
|
||||
{
|
||||
$request = Request::create('/');
|
||||
$request->replace([
|
||||
'test' => "foo\rbar"
|
||||
]);
|
||||
|
||||
$middleware = new NormalizeLineEnding();
|
||||
|
||||
$middleware->handle($request, function (Request $request) {
|
||||
$this->assertEquals("foo\nbar", $request->input('test'));
|
||||
});
|
||||
}
|
||||
|
||||
public function testLFtoLF()
|
||||
{
|
||||
$request = Request::create('/');
|
||||
$request->replace([
|
||||
'test' => "foo\nbar"
|
||||
]);
|
||||
|
||||
$middleware = new NormalizeLineEnding();
|
||||
|
||||
$middleware->handle($request, function (Request $request) {
|
||||
$this->assertEquals("foo\nbar", $request->input('test'));
|
||||
});
|
||||
}
|
||||
|
||||
public function testArrayRequest()
|
||||
{
|
||||
$request = Request::create('/');
|
||||
$request->replace([
|
||||
'test' => "foo\r\nbar",
|
||||
'hash' => [
|
||||
'yuzuki' => "yuzuki\r\nyukari",
|
||||
'miku' => "hatsune\r\nmiku",
|
||||
],
|
||||
'array' => [
|
||||
"kagamine\r\nrin",
|
||||
"kagamine\r\nlen"
|
||||
]
|
||||
]);
|
||||
|
||||
$middleware = new NormalizeLineEnding();
|
||||
|
||||
$middleware->handle($request, function (Request $request) {
|
||||
$this->assertEquals("foo\nbar", $request->input('test'));
|
||||
$this->assertEquals("yuzuki\nyukari", $request->input('hash.yuzuki'));
|
||||
$this->assertEquals("hatsune\nmiku", $request->input('hash.miku'));
|
||||
$this->assertEquals("kagamine\nrin", $request->input('array.0'));
|
||||
$this->assertEquals("kagamine\nlen", $request->input('array.1'));
|
||||
});
|
||||
}
|
||||
}
|
@ -25,8 +25,8 @@ class DLsiteResolverTest extends TestCase
|
||||
$this->createResolver(DLsiteResolver::class, $responseText);
|
||||
|
||||
$metadata = $this->resolver->resolve('https://www.dlsite.com/maniax/work/=/product_id/RJ171695.html');
|
||||
$this->assertEquals('【骨伝導風】道草屋 たびらこ-一緒にはみがき【耳かき&はみがき】 [桃色CODE] | DLsite', $metadata->title);
|
||||
$this->assertStringStartsWith('少しお母さんっぽい店員さんに、歯磨きからおやすみまでお世話されます。はみがきで興奮しちゃった旦那様のも、しっかりお世話してくれます。歯磨き音は特殊なマイクを使用、骨伝導風ハイレゾバイノーラル音声です。', $metadata->description);
|
||||
$this->assertEquals('【骨伝導風】道草屋 たびらこ-一緒にはみがき【耳かき&はみがき】', $metadata->title);
|
||||
$this->assertStringEndsWith('少しお母さんっぽい店員さんに、歯磨きからおやすみまでお世話されます。はみがきで興奮しちゃった旦那様のも、しっかりお世話してくれます。歯磨き音は特殊なマイクを使用、骨伝導風ハイレゾバイノーラル音声です。', $metadata->description);
|
||||
$this->assertEquals('https://img.dlsite.jp/modpub/images2/work/doujin/RJ172000/RJ171695_img_main.jpg', $metadata->image);
|
||||
if ($this->shouldUseMock()) {
|
||||
$this->assertSame('https://www.dlsite.com/maniax/work/=/product_id/RJ171695.html', (string) $this->handler->getLastRequest()->getUri());
|
||||
@ -40,11 +40,26 @@ class DLsiteResolverTest extends TestCase
|
||||
$this->createResolver(DLsiteResolver::class, $responseText);
|
||||
|
||||
$metadata = $this->resolver->resolve('https://www.dlsite.com/home/work/=/product_id/RJ234446.html');
|
||||
$this->assertEquals('【大人向け耳かき】道草屋 はこべら5 時計修理のはこべらさん。他【汗の匂い】 [桃色CODE] | DLsite', $metadata->title);
|
||||
$this->assertStringStartsWith('夏の終わり、二人で遠くの花火を眺めます。耳かきの他、クラシックシェービング、氷を含んだあまがみ、冷紅茶、ジャズ、時計の修理、それから大人向けの汗の匂い。色々な事のある、二泊三日の田舎宿音声です。', $metadata->description);
|
||||
$this->assertEquals('【大人向け耳かき】道草屋 はこべら5 時計修理のはこべらさん。他【汗の匂い】', $metadata->title);
|
||||
$this->assertStringEndsWith('夏の終わり、二人で遠くの花火を眺めます。耳かきの他、クラシックシェービング、氷を含んだあまがみ、冷紅茶、ジャズ、時計の修理、それから大人向けの汗の匂い。色々な事のある、二泊三日の田舎宿音声です。', $metadata->description);
|
||||
$this->assertEquals('https://img.dlsite.jp/modpub/images2/work/doujin/RJ235000/RJ234446_img_main.jpg', $metadata->image);
|
||||
if ($this->shouldUseMock()) {
|
||||
$this->assertSame('https://www.dlsite.com/home/work/=/product_id/RJ234446.html', (string) $this->handler->getLastRequest()->getUri());
|
||||
}
|
||||
}
|
||||
|
||||
public function testProductShortLink()
|
||||
{
|
||||
$responseText = file_get_contents(__DIR__.'/../../fixture/DLsite/testProduct.html');
|
||||
|
||||
$this->createResolver(DLsiteResolver::class, $responseText);
|
||||
|
||||
$metadata = $this->resolver->resolve('https://dlsite.jp/mawtw/RJ171695.html');
|
||||
$this->assertEquals('【骨伝導風】道草屋 たびらこ-一緒にはみがき【耳かき&はみがき】', $metadata->title);
|
||||
$this->assertStringEndsWith('少しお母さんっぽい店員さんに、歯磨きからおやすみまでお世話されます。はみがきで興奮しちゃった旦那様のも、しっかりお世話してくれます。歯磨き音は特殊なマイクを使用、骨伝導風ハイレゾバイノーラル音声です。', $metadata->description);
|
||||
$this->assertEquals('https://img.dlsite.jp/modpub/images2/work/doujin/RJ172000/RJ171695_img_main.jpg', $metadata->image);
|
||||
if ($this->shouldUseMock()) {
|
||||
$this->assertSame('https://dlsite.jp/mawtw/RJ171695.html', (string) $this->handler->getLastRequest()->getUri());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
50
tests/Unit/MetadataResolver/FC2ContentsResolverTest.php
Normal file
50
tests/Unit/MetadataResolver/FC2ContentsResolverTest.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\MetadataResolver;
|
||||
|
||||
use App\MetadataResolver\FC2ContentsResolver;
|
||||
use Tests\TestCase;
|
||||
|
||||
class FC2ContentsResolverTest extends TestCase
|
||||
{
|
||||
use CreateMockedResolver;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
if (!$this->shouldUseMock()) {
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
public function testAdult()
|
||||
{
|
||||
$responseText = file_get_contents(__DIR__.'/../../fixture/FC2Contents/adult.html');
|
||||
|
||||
$this->createResolver(FC2ContentsResolver::class, $responseText);
|
||||
|
||||
$metadata = $this->resolver->resolve('https://adult.contents.fc2.com/article_search.php?id=401545');
|
||||
$this->assertEquals('個人撮影@「ぱいずりオアトリート♡」Jカップ魔女っ子の3連挟射しても続けちゃうパイズリ!', $metadata->title);
|
||||
$this->assertEquals('個人撮影@「ぱいずりオアトリート♡」Jカップ魔女っ子の3連挟射しても続けちゃうパイズリ! - イベントコスチュームということもあり、大ボリュームだった前回、前々回の パイズリ役Jcupメイド と ナースパイズリを超え 今回さらに超ボリューム&超密度の内容になってます! -------- …', $metadata->description);
|
||||
$this->assertEquals('https://storage2000.contents.fc2.com/file/104/10362633/1477676255.72.png', $metadata->image);
|
||||
if ($this->shouldUseMock()) {
|
||||
$this->assertSame('https://adult.contents.fc2.com/article_search.php?id=401545', (string) $this->handler->getLastRequest()->getUri());
|
||||
}
|
||||
}
|
||||
|
||||
public function testGeneral()
|
||||
{
|
||||
$responseText = file_get_contents(__DIR__.'/../../fixture/FC2Contents/general.html');
|
||||
|
||||
$this->createResolver(FC2ContentsResolver::class, $responseText);
|
||||
|
||||
$metadata = $this->resolver->resolve('https://contents.fc2.com/article_search.php?id=336610');
|
||||
$this->assertEquals('ゆかいなどうぶつたち ~オオカミ・キツネ・タヌキ~', $metadata->title);
|
||||
$this->assertEquals('ゆかいなどうぶつたち ~オオカミ・キツネ・タヌキ~ - 今回のおともだちは、オオカミ・キツネ・タヌキだよ。地球上に住んでいるたくさんのおともだち、みんなにどんどん紹介するからたのしみにしてね!', $metadata->description);
|
||||
$this->assertEquals('https://storage6000.contents.fc2.com/file/300/29917555/1519118184.65.jpg', $metadata->image);
|
||||
if ($this->shouldUseMock()) {
|
||||
$this->assertSame('https://contents.fc2.com/article_search.php?id=336610', (string) $this->handler->getLastRequest()->getUri());
|
||||
}
|
||||
}
|
||||
}
|
35
tests/Unit/MetadataResolver/PlurkResolverTest.php
Normal file
35
tests/Unit/MetadataResolver/PlurkResolverTest.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\MetadataResolver;
|
||||
|
||||
use App\MetadataResolver\PlurkResolver;
|
||||
use Tests\TestCase;
|
||||
|
||||
class PlurkResolverTest extends TestCase
|
||||
{
|
||||
use CreateMockedResolver;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
if (!$this->shouldUseMock()) {
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
public function test()
|
||||
{
|
||||
$responseText = file_get_contents(__DIR__.'/../../fixture/Plurk/test.html');
|
||||
|
||||
$this->createResolver(PlurkResolver::class, $responseText);
|
||||
|
||||
$metadata = $this->resolver->resolve('https://www.plurk.com/p/n0awli/');
|
||||
$this->assertEquals('[R18]FC2實況中', $metadata->title);
|
||||
$this->assertEquals('Plurk by 小虫/ムシ@台中種 - 71 response(s)', $metadata->description);
|
||||
$this->assertEquals('https://images.plurk.com/5cT15Sf9OOFYk9fEQ759bZ.jpg', $metadata->image);
|
||||
if ($this->shouldUseMock()) {
|
||||
$this->assertSame('https://www.plurk.com/p/n0awli/', (string) $this->handler->getLastRequest()->getUri());
|
||||
}
|
||||
}
|
||||
}
|
15
tests/fixture/FC2Contents/adult.html
Normal file
15
tests/fixture/FC2Contents/adult.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ja">
|
||||
<head>
|
||||
<title>個人撮影@「ぱいずりオアトリート♡」Jカップ魔女っ子の3連挟射しても続けちゃうパイズリ!</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="description" content="個人撮影@「ぱいずりオアトリート♡」Jカップ魔女っ子の3連挟射しても続けちゃうパイズリ! - イベントコスチュームということもあり、大ボリュームだった前回、前々回の パイズリ役Jcupメイド と ナースパイズリを超え 今回さらに超ボリューム&超密度の内容になってます! -------- …">
|
||||
</head>
|
||||
<body>
|
||||
<div class="main_thum_img">
|
||||
<a class="analyticsLinkClick_mainThum" href="http://storage2000.contents.fc2.com/file/104/10362633/1477676255.72.png">
|
||||
<img src="//contents-thumbnail2.fc2.com/w276/storage2000.contents.fc2.com/file/104/10362633/1477676255.72.png">
|
||||
</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
15
tests/fixture/FC2Contents/general.html
Normal file
15
tests/fixture/FC2Contents/general.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ja">
|
||||
<head>
|
||||
<title>ゆかいなどうぶつたち ~オオカミ・キツネ・タヌキ~</title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="description" content="ゆかいなどうぶつたち ~オオカミ・キツネ・タヌキ~ - 今回のおともだちは、オオカミ・キツネ・タヌキだよ。地球上に住んでいるたくさんのおともだち、みんなにどんどん紹介するからたのしみにしてね!"/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="main_thum_img">
|
||||
<a class="analyticsLinkClick_mainThum" href="http://storage6000.contents.fc2.com/file/300/29917555/1519118184.65.jpg">
|
||||
<img src="//contents-thumbnail2.fc2.com/w276/storage6000.contents.fc2.com/file/300/29917555/1519118184.65.jpg"/>
|
||||
</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
42
tests/fixture/Plurk/test.html
Normal file
42
tests/fixture/Plurk/test.html
Normal file
@ -0,0 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>小虫/ムシ@台中種 - [R18]FC2實況中 - Plurk</title>
|
||||
<link rel="shortcut icon" type="image/png" href="//s.plurk.com/936ddc656e104792b651240cdafeb7aa.png">
|
||||
<link rel="dns-prefetch" href="//avatars.plurk.com">
|
||||
<link rel="dns-prefetch" href="//emos.plurk.com">
|
||||
<link rel="dns-prefetch" href="//images.plurk.com">
|
||||
<link rel="dns-prefetch" href="//imgs.plurk.com">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=0" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="fragment" content="!">
|
||||
<meta property="og:type" content="article" />
|
||||
<meta property="og:title" content="[R18]FC2實況中" />
|
||||
<meta property="og:site_name" content="Plurk" />
|
||||
<meta property="og:url" content="https://www.plurk.com/p/n0awli" />
|
||||
<meta property="og:description" content="Plurk by 小虫/ムシ@台中種 - 71 response(s)" />
|
||||
<meta property="og:image" content="https://s.plurk.com/6c6e2fb987651802af50e5f6a3853b40.png" />
|
||||
<meta property="fb:app_id" content="47804741521"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" />
|
||||
<meta name="verify-v1" content="iBRwaQ/3d4NoF1uaa2SAfCJ962ORry1TE8/4XxtIbHk=" />
|
||||
<meta name="viewport" content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, width=device-width">
|
||||
<meta name="application-name" content="Plurk"/>
|
||||
<meta name="msapplication-TileColor" content="#AA460F"/>
|
||||
<meta name="msapplication-TileImage" content="//s.plurk.com/0964d8f7301cc4ee38b343ed154d2369.png"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="content">
|
||||
<div class="text_holder">
|
||||
<a href="https://images.plurk.com/5cT15Sf9OOFYk9fEQ759bZ.jpg" class="ex_link pictureservices" rel="nofollow">
|
||||
<img src="https://images.plurk.com/mx_5cT15Sf9OOFYk9fEQ759bZ.jpg" alt="https://images.plurk.com/5cT15Sf9OOFYk9fEQ759bZ.jpg" height="48">
|
||||
</a>
|
||||
<a href="https://images.plurk.com/2HdBlulzzXMZB7vITj4uOG.jpg" class="ex_link pictureservices" rel="nofollow">
|
||||
<img src="https://images.plurk.com/mx_2HdBlulzzXMZB7vITj4uOG.jpg" alt="https://images.plurk.com/2HdBlulzzXMZB7vITj4uOG.jpg" height="48">
|
||||
</a> [R18]FC2實況中
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
14
webpack.mix.js
vendored
14
webpack.mix.js
vendored
@ -1,4 +1,5 @@
|
||||
let mix = require('laravel-mix');
|
||||
const mix = require('laravel-mix');
|
||||
require('laravel-mix-bundle-analyzer')
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@ -13,10 +14,17 @@ let mix = require('laravel-mix');
|
||||
|
||||
mix.js('resources/assets/js/app.js', 'public/js')
|
||||
.js('resources/assets/js/home.js', 'public/js')
|
||||
.js('resources/assets/js/checkin.js', 'public/js')
|
||||
.js('resources/assets/js/user/stats.js', 'public/js/user')
|
||||
.js('resources/assets/js/setting/privacy.js', 'public/js/setting')
|
||||
.js('resources/assets/js/checkin.js', 'public/js')
|
||||
.sass('resources/assets/sass/app.scss', 'public/css')
|
||||
.autoload({
|
||||
'jquery': ['$', 'jQuery', 'window.jQuery']
|
||||
});
|
||||
})
|
||||
.extract(['jquery', 'bootstrap'])
|
||||
.extract(['chart.js', 'chartjs-color', 'color-name', 'moment'], 'public/js/vendor/chart')
|
||||
.version();
|
||||
|
||||
if (process.argv.includes('-a')) {
|
||||
mix.bundleAnalyzer({analyzerMode: 'static'});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user