commit
65cb2127ef
@ -105,6 +105,12 @@ jobs:
|
|||||||
command: yarn run stylelint
|
command: yarn run stylelint
|
||||||
when: always
|
when: always
|
||||||
|
|
||||||
|
# Run eslint
|
||||||
|
- run:
|
||||||
|
name: eslint
|
||||||
|
command: yarn run eslint
|
||||||
|
when: always
|
||||||
|
|
||||||
# Run unit test
|
# Run unit test
|
||||||
- run:
|
- run:
|
||||||
command: |
|
command: |
|
||||||
|
28
.eslintrc.js
vendored
Normal file
28
.eslintrc.js
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
module.exports = {
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
es2020: true,
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:vue/essential',
|
||||||
|
'plugin:prettier/recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'prettier',
|
||||||
|
'prettier/@typescript-eslint',
|
||||||
|
'prettier/vue',
|
||||||
|
],
|
||||||
|
parser: 'vue-eslint-parser',
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 11,
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
plugins: ['prettier', 'vue', '@typescript-eslint'],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/explicit-module-boundary-types': 0,
|
||||||
|
'@typescript-eslint/no-explicit-any': 0,
|
||||||
|
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
|
||||||
|
},
|
||||||
|
};
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,6 +5,7 @@
|
|||||||
/public/hot
|
/public/hot
|
||||||
/public/storage
|
/public/storage
|
||||||
/public/mix-manifest.json
|
/public/mix-manifest.json
|
||||||
|
/public/report.html
|
||||||
/storage/*.key
|
/storage/*.key
|
||||||
/vendor
|
/vendor
|
||||||
/.idea
|
/.idea
|
||||||
|
5
.prettierrc
Normal file
5
.prettierrc
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"arrowParens": "always",
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 120
|
||||||
|
}
|
@ -3,6 +3,7 @@
|
|||||||
namespace App\MetadataResolver;
|
namespace App\MetadataResolver;
|
||||||
|
|
||||||
use GuzzleHttp\Client;
|
use GuzzleHttp\Client;
|
||||||
|
use GuzzleHttp\Cookie\CookieJar;
|
||||||
use Symfony\Component\DomCrawler\Crawler;
|
use Symfony\Component\DomCrawler\Crawler;
|
||||||
|
|
||||||
class FanzaResolver implements Resolver
|
class FanzaResolver implements Resolver
|
||||||
@ -43,7 +44,9 @@ class FanzaResolver implements Resolver
|
|||||||
|
|
||||||
public function resolve(string $url): Metadata
|
public function resolve(string $url): Metadata
|
||||||
{
|
{
|
||||||
$res = $this->client->get($url);
|
$cookieJar = CookieJar::fromArray(['age_check_done' => '1'], 'dmm.co.jp');
|
||||||
|
|
||||||
|
$res = $this->client->get($url, ['cookies' => $cookieJar]);
|
||||||
$html = (string) $res->getBody();
|
$html = (string) $res->getBody();
|
||||||
$crawler = new Crawler($html);
|
$crawler = new Crawler($html);
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ class MetadataResolver implements Resolver
|
|||||||
'~dmm\.co\.jp/~' => FanzaResolver::class,
|
'~dmm\.co\.jp/~' => FanzaResolver::class,
|
||||||
'~www\.patreon\.com/~' => PatreonResolver::class,
|
'~www\.patreon\.com/~' => PatreonResolver::class,
|
||||||
'~www\.deviantart\.com/.*/art/.*~' => DeviantArtResolver::class,
|
'~www\.deviantart\.com/.*/art/.*~' => DeviantArtResolver::class,
|
||||||
'~\.syosetu\.com/n\d+[a-z]{2,}~' => NarouResolver::class,
|
'~\.syosetu\.com/n\d+[a-z]+~' => NarouResolver::class,
|
||||||
'~ci-en\.(jp|net|dlsite\.com)/creator/\d+/article/\d+~' => CienResolver::class,
|
'~ci-en\.(jp|net|dlsite\.com)/creator/\d+/article/\d+~' => CienResolver::class,
|
||||||
'~www\.plurk\.com\/p\/.*~' => PlurkResolver::class,
|
'~www\.plurk\.com\/p\/.*~' => PlurkResolver::class,
|
||||||
'~(adult\.)?contents\.fc2\.com\/article_search\.php\?id=\d+~' => FC2ContentsResolver::class,
|
'~(adult\.)?contents\.fc2\.com\/article_search\.php\?id=\d+~' => FC2ContentsResolver::class,
|
||||||
|
1132
composer.lock
generated
1132
composer.lock
generated
File diff suppressed because it is too large
Load Diff
26
package.json
26
package.json
@ -8,15 +8,26 @@
|
|||||||
"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",
|
"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",
|
"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",
|
||||||
|
"eslint": "eslint --ext .js,.ts,.vue resources/",
|
||||||
"stylelint": "stylelint resources/assets/sass/**/*"
|
"stylelint": "stylelint resources/assets/sass/**/*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/bootstrap": "^4.5.0",
|
||||||
|
"@types/cal-heatmap": "^3.3.10",
|
||||||
|
"@types/chart.js": "^2.9.22",
|
||||||
"@types/jquery": "^3.3.38",
|
"@types/jquery": "^3.3.38",
|
||||||
"bootstrap": "^4.3.1",
|
"@types/js-cookie": "^2.2.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^3.1.0",
|
||||||
|
"@typescript-eslint/parser": "^3.1.0",
|
||||||
|
"bootstrap": "^4.5.0",
|
||||||
"cal-heatmap": "^3.3.10",
|
"cal-heatmap": "^3.3.10",
|
||||||
"chart.js": "^2.7.1",
|
"chart.js": "^2.7.1",
|
||||||
"cross-env": "^5.2.0",
|
"cross-env": "^5.2.0",
|
||||||
"date-fns": "^1.30.1",
|
"date-fns": "^1.30.1",
|
||||||
|
"eslint": "^7.2.0",
|
||||||
|
"eslint-config-prettier": "^6.11.0",
|
||||||
|
"eslint-plugin-prettier": "^3.1.3",
|
||||||
|
"eslint-plugin-vue": "^6.2.2",
|
||||||
"husky": "^1.3.1",
|
"husky": "^1.3.1",
|
||||||
"jquery": "^3.5.0",
|
"jquery": "^3.5.0",
|
||||||
"js-cookie": "^2.2.0",
|
"js-cookie": "^2.2.0",
|
||||||
@ -25,16 +36,17 @@
|
|||||||
"lint-staged": "^8.1.5",
|
"lint-staged": "^8.1.5",
|
||||||
"open-iconic": "^1.1.1",
|
"open-iconic": "^1.1.1",
|
||||||
"popper.js": "^1.14.7",
|
"popper.js": "^1.14.7",
|
||||||
"resolve-url-loader": "^2.3.1",
|
"prettier": "^2.0.5",
|
||||||
"sass": "^1.17.0",
|
"resolve-url-loader": "^3.1.1",
|
||||||
|
"sass": "^1.26.8",
|
||||||
"sass-loader": "^7.1.0",
|
"sass-loader": "^7.1.0",
|
||||||
"stylelint": "^9.10.1",
|
"stylelint": "^9.10.1",
|
||||||
"stylelint-config-recess-order": "^2.0.1",
|
"stylelint-config-recess-order": "^2.0.4",
|
||||||
"ts-loader": "^6.0.1",
|
"ts-loader": "^6.0.1",
|
||||||
"typescript": "^3.4.5",
|
"typescript": "^3.4.5",
|
||||||
"vue": "^2.6.10",
|
"vue": "^2.6.10",
|
||||||
"vue-class-component": "^7.1.0",
|
"vue-class-component": "^7.1.0",
|
||||||
"vue-property-decorator": "^8.1.1",
|
"vue-property-decorator": "^9.0.0",
|
||||||
"vue-template-compiler": "^2.6.10"
|
"vue-template-compiler": "^2.6.10"
|
||||||
},
|
},
|
||||||
"stylelint": {
|
"stylelint": {
|
||||||
@ -50,6 +62,10 @@
|
|||||||
"stylelint --fix",
|
"stylelint --fix",
|
||||||
"git add"
|
"git add"
|
||||||
],
|
],
|
||||||
|
"*.{ts,js,vue}" : [
|
||||||
|
"eslint --fix",
|
||||||
|
"git add"
|
||||||
|
],
|
||||||
"*.php": [
|
"*.php": [
|
||||||
"composer fix",
|
"composer fix",
|
||||||
"git add"
|
"git add"
|
||||||
|
4
resources/assets/js/@types/cal-heatmap.d.ts
vendored
Normal file
4
resources/assets/js/@types/cal-heatmap.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
// なんか @types/cal-heatmap 入れても動かなかったんですけど!!
|
||||||
|
declare module 'cal-heatmap' {
|
||||||
|
export = CalHeatMap;
|
||||||
|
}
|
4
resources/assets/js/@types/jquery-bootstrap.d.ts
vendored
Normal file
4
resources/assets/js/@types/jquery-bootstrap.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
// @types/bootstrap に足りないもの
|
||||||
|
interface JQuery<TElement = HTMLElement> {
|
||||||
|
modal(action: 'toggle' | 'show' | 'hide' | 'handleUpdate' | 'dispose', relatedTarget?: TElement): this;
|
||||||
|
}
|
12
resources/assets/js/@types/jquery-tissue.d.ts
vendored
Normal file
12
resources/assets/js/@types/jquery-tissue.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// tissue.ts で定義されているjQuery Pluginの型定義
|
||||||
|
declare namespace JQueryTissue {
|
||||||
|
interface LinkCardOptions {
|
||||||
|
endpoint: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface JQuery<TElement = HTMLElement> {
|
||||||
|
linkCard: (options?: JQueryTissue.LinkCardOptions) => this;
|
||||||
|
pageSelector: () => this;
|
||||||
|
deleteCheckinModal: () => this;
|
||||||
|
}
|
4
resources/assets/js/@types/vue-shims.d.ts
vendored
Normal file
4
resources/assets/js/@types/vue-shims.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
declare module '*.vue' {
|
||||||
|
import Vue from 'vue';
|
||||||
|
export default Vue;
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import Cookies from 'js-cookie';
|
import * as Cookies from 'js-cookie';
|
||||||
|
import jqXHR = JQuery.jqXHR;
|
||||||
|
|
||||||
require('./bootstrap');
|
require('./bootstrap');
|
||||||
|
|
||||||
@ -7,10 +8,10 @@ $(() => {
|
|||||||
$('body').removeClass('tis-need-agecheck');
|
$('body').removeClass('tis-need-agecheck');
|
||||||
} else {
|
} else {
|
||||||
$('#ageCheckModal')
|
$('#ageCheckModal')
|
||||||
.modal({backdrop: 'static'})
|
.modal({ backdrop: 'static' })
|
||||||
.on('hide.bs.modal', function () {
|
.on('hide.bs.modal', function () {
|
||||||
$('body').removeClass('tis-need-agecheck');
|
$('body').removeClass('tis-need-agecheck');
|
||||||
Cookies.set('agechecked', '1', {expires: 365});
|
Cookies.set('agechecked', '1', { expires: 365 });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,7 +29,7 @@ $(() => {
|
|||||||
$deleteCheckinModal.modal('show', this);
|
$deleteCheckinModal.modal('show', this);
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).on('click', '[data-href]', function (event) {
|
$(document).on('click', '[data-href]', function (_event) {
|
||||||
location.href = $(this).data('href');
|
location.href = $(this).data('href');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -40,7 +41,7 @@ $(() => {
|
|||||||
const isLiked = $this.data('liked');
|
const isLiked = $this.data('liked');
|
||||||
|
|
||||||
if (isLiked) {
|
if (isLiked) {
|
||||||
const callback = (data) => {
|
const callback = (data: any) => {
|
||||||
$this.data('liked', false);
|
$this.data('liked', false);
|
||||||
$this.find('.oi-heart').removeClass('text-danger');
|
$this.find('.oi-heart').removeClass('text-danger');
|
||||||
|
|
||||||
@ -51,10 +52,10 @@ $(() => {
|
|||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/api/likes/' + encodeURIComponent(targetId),
|
url: '/api/likes/' + encodeURIComponent(targetId),
|
||||||
method: 'delete',
|
method: 'delete',
|
||||||
type: 'json'
|
type: 'json',
|
||||||
})
|
})
|
||||||
.then(callback)
|
.then(callback)
|
||||||
.catch(function (xhr) {
|
.catch(function (xhr: jqXHR) {
|
||||||
if (xhr.status === 404) {
|
if (xhr.status === 404) {
|
||||||
callback(JSON.parse(xhr.responseText));
|
callback(JSON.parse(xhr.responseText));
|
||||||
return;
|
return;
|
||||||
@ -64,7 +65,7 @@ $(() => {
|
|||||||
alert('いいねを解除できませんでした。');
|
alert('いいねを解除できませんでした。');
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const callback = (data) => {
|
const callback = (data: any) => {
|
||||||
$this.data('liked', true);
|
$this.data('liked', true);
|
||||||
$this.find('.oi-heart').addClass('text-danger');
|
$this.find('.oi-heart').addClass('text-danger');
|
||||||
|
|
||||||
@ -77,11 +78,11 @@ $(() => {
|
|||||||
method: 'post',
|
method: 'post',
|
||||||
type: 'json',
|
type: 'json',
|
||||||
data: {
|
data: {
|
||||||
id: targetId
|
id: targetId,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
.then(callback)
|
.then(callback)
|
||||||
.catch(function (xhr) {
|
.catch(function (xhr: jqXHR) {
|
||||||
if (xhr.status === 409) {
|
if (xhr.status === 409) {
|
||||||
callback(JSON.parse(xhr.responseText));
|
callback(JSON.parse(xhr.responseText));
|
||||||
return;
|
return;
|
||||||
@ -96,9 +97,9 @@ $(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).on('click', '.card-spoiler-overlay', function (event) {
|
$(document).on('click', '.card-spoiler-overlay', function (_event) {
|
||||||
const $this = $(this);
|
const $this = $(this);
|
||||||
$this.siblings(".card-link").removeClass("card-spoiler");
|
$this.siblings('.card-link').removeClass('card-spoiler');
|
||||||
$this.remove();
|
$this.remove();
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -2,16 +2,16 @@
|
|||||||
import './tissue';
|
import './tissue';
|
||||||
|
|
||||||
// Setup global request header
|
// Setup global request header
|
||||||
const token = document.head.querySelector('meta[name="csrf-token"]');
|
const token = document.head.querySelector<HTMLMetaElement>('meta[name="csrf-token"]');
|
||||||
if (!token) {
|
if (!token) {
|
||||||
console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
|
console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
|
||||||
|
} else {
|
||||||
|
$.ajaxSetup({
|
||||||
|
headers: {
|
||||||
|
'X-CSRF-TOKEN': token.content,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$.ajaxSetup({
|
|
||||||
headers: {
|
|
||||||
'X-CSRF-TOKEN': token.content
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Bootstrap
|
// Bootstrap
|
||||||
import 'bootstrap';
|
import 'bootstrap';
|
@ -1,8 +1,8 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import TagInput from "./components/TagInput.vue";
|
import TagInput from './components/TagInput.vue';
|
||||||
import MetadataPreview from './components/MetadataPreview.vue';
|
import MetadataPreview from './components/MetadataPreview.vue';
|
||||||
|
|
||||||
export const bus = new Vue({name: "EventBus"});
|
export const bus = new Vue({ name: 'EventBus' });
|
||||||
|
|
||||||
export enum MetadataLoadState {
|
export enum MetadataLoadState {
|
||||||
Inactive,
|
Inactive,
|
||||||
@ -19,11 +19,11 @@ new Vue({
|
|||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
TagInput,
|
TagInput,
|
||||||
MetadataPreview
|
MetadataPreview,
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
// オカズリンクにURLがセットされている場合は、すぐにメタデータを取得する
|
// オカズリンクにURLがセットされている場合は、すぐにメタデータを取得する
|
||||||
const linkInput = this.$el.querySelector<HTMLInputElement>("#link");
|
const linkInput = this.$el.querySelector<HTMLInputElement>('#link');
|
||||||
if (linkInput && /^https?:\/\//.test(linkInput.value)) {
|
if (linkInput && /^https?:\/\//.test(linkInput.value)) {
|
||||||
this.fetchMetadata(linkInput.value);
|
this.fetchMetadata(linkInput.value);
|
||||||
}
|
}
|
||||||
@ -52,15 +52,17 @@ new Vue({
|
|||||||
method: 'get',
|
method: 'get',
|
||||||
type: 'json',
|
type: 'json',
|
||||||
data: {
|
data: {
|
||||||
url
|
url,
|
||||||
}
|
},
|
||||||
}).then(data => {
|
})
|
||||||
this.metadata = data;
|
.then((data) => {
|
||||||
this.metadataLoadState = MetadataLoadState.Success;
|
this.metadata = data;
|
||||||
}).catch(e => {
|
this.metadataLoadState = MetadataLoadState.Success;
|
||||||
this.metadata = null;
|
})
|
||||||
this.metadataLoadState = MetadataLoadState.Failed;
|
.catch((_e) => {
|
||||||
});
|
this.metadata = null;
|
||||||
}
|
this.metadataLoadState = MetadataLoadState.Failed;
|
||||||
}
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
@ -5,23 +5,34 @@
|
|||||||
<div v-if="state === MetadataLoadState.Loading" class="row no-gutters">
|
<div v-if="state === MetadataLoadState.Loading" class="row no-gutters">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card-body">
|
<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>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="state === MetadataLoadState.Success" class="row no-gutters">
|
<div v-else-if="state === MetadataLoadState.Success" class="row no-gutters">
|
||||||
<div v-if="hasImage" class="col-4 justify-content-center align-items-center">
|
<div v-if="hasImage" class="col-4 justify-content-center align-items-center">
|
||||||
<img :src="metadata.image" alt="Thumbnail" class="w-100 bg-secondary">
|
<img :src="metadata.image" alt="Thumbnail" class="w-100 bg-secondary" />
|
||||||
</div>
|
</div>
|
||||||
<div :class="descClasses">
|
<div :class="descClasses">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h6 class="card-title font-weight-bold" style="font-size: small;">{{ metadata.title }}</h6>
|
<h6 class="card-title font-weight-bold" style="font-size: small;">{{ metadata.title }}</h6>
|
||||||
<template v-if="suggestions.length > 0">
|
<template v-if="suggestions.length > 0">
|
||||||
<p class="card-text mb-2" style="font-size: small;">タグ候補<br><span class="text-secondary">(クリックするとタグ入力欄にコピーできます)</span></p>
|
<p class="card-text mb-2" style="font-size: small;">
|
||||||
|
タグ候補<br /><span class="text-secondary"
|
||||||
|
>(クリックするとタグ入力欄にコピーできます)</span
|
||||||
|
>
|
||||||
|
</p>
|
||||||
<ul class="list-inline d-inline">
|
<ul class="list-inline d-inline">
|
||||||
<li v-for="tag in suggestions"
|
<li
|
||||||
|
v-for="tag in suggestions"
|
||||||
:class="tagClasses(tag)"
|
:class="tagClasses(tag)"
|
||||||
@click="addTag(tag.name)"><span class="oi oi-tag"></span> {{ tag.name }}</li>
|
@click="addTag(tag.name)"
|
||||||
|
:key="tag.name"
|
||||||
|
>
|
||||||
|
<span class="oi oi-tag"></span> {{ tag.name }}
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@ -30,7 +41,9 @@
|
|||||||
<div v-else class="row no-gutters">
|
<div v-else class="row no-gutters">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card-body">
|
<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>
|
<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>
|
||||||
@ -40,104 +53,104 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {Vue, Component, Prop} from "vue-property-decorator";
|
import { Vue, Component, Prop } from 'vue-property-decorator';
|
||||||
import {bus, MetadataLoadState} from "../checkin";
|
import { bus, MetadataLoadState } from '../checkin';
|
||||||
|
|
||||||
type Metadata = {
|
type Metadata = {
|
||||||
url: string,
|
url: string;
|
||||||
title: string,
|
title: string;
|
||||||
description: string,
|
description: string;
|
||||||
image: string,
|
image: string;
|
||||||
expires_at: string | null,
|
expires_at: string | null;
|
||||||
tags: {
|
tags: {
|
||||||
name: string
|
name: string;
|
||||||
}[],
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type Suggestion = {
|
type Suggestion = {
|
||||||
name: string,
|
name: string;
|
||||||
used: boolean,
|
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(): void {
|
||||||
|
bus.$on('change-tag', (tags: string[]) => (this.tags = tags));
|
||||||
|
bus.$emit('resend-tag');
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component
|
addTag(tag: string): void {
|
||||||
export default class MetadataPreview extends Vue {
|
bus.$emit('add-tag', tag);
|
||||||
@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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.link-card-mini {
|
.link-card-mini {
|
||||||
$height: 150px;
|
$height: 150px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.row > div:first-child {
|
.row > div:first-child {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
&:not([display=none]) {
|
&:not([display='none']) {
|
||||||
min-height: $height;
|
min-height: $height;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-text {
|
|
||||||
white-space: pre-line;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.metadata-tag-item {
|
.card-text {
|
||||||
cursor: pointer;
|
white-space: pre-line;
|
||||||
user-select: none;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.metadata-tag-item {
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,96 +1,96 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :class="containerClass" @click="$refs.input.focus()">
|
<div :class="containerClass" @click="$refs.input.focus()">
|
||||||
<input :name="name" type="hidden" :value="tagValue">
|
<input :name="name" type="hidden" :value="tagValue" />
|
||||||
<ul class="list-inline d-inline">
|
<ul class="list-inline d-inline">
|
||||||
<li v-for="(tag, i) in tags"
|
<li
|
||||||
|
v-for="(tag, i) in tags"
|
||||||
class="list-inline-item badge badge-primary tag-item"
|
class="list-inline-item badge badge-primary tag-item"
|
||||||
@click="removeTag(i)"><span class="oi oi-tag"></span> {{ tag }} | x</li>
|
@click="removeTag(i)"
|
||||||
|
:key="tag"
|
||||||
|
>
|
||||||
|
<span class="oi oi-tag"></span> {{ tag }} | x
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<input :id="id"
|
<input :id="id" ref="input" type="text" class="tag-input" v-model="buffer" @keydown="onKeyDown" />
|
||||||
ref="input"
|
|
||||||
type="text"
|
|
||||||
class="tag-input"
|
|
||||||
v-model="buffer"
|
|
||||||
@keydown="onKeyDown">
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {Vue, Component, Prop, Watch} from "vue-property-decorator";
|
import { Vue, Component, Prop, Watch } from 'vue-property-decorator';
|
||||||
import {bus} from "../checkin";
|
import { bus } from '../checkin';
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
export default class TagInput extends Vue {
|
export default class TagInput extends Vue {
|
||||||
@Prop(String) readonly id!: string;
|
@Prop(String) readonly id!: string;
|
||||||
@Prop(String) readonly name!: string;
|
@Prop(String) readonly name!: string;
|
||||||
@Prop(String) readonly value!: string;
|
@Prop(String) readonly value!: string;
|
||||||
@Prop(Boolean) readonly isInvalid!: boolean;
|
@Prop(Boolean) readonly isInvalid!: boolean;
|
||||||
|
|
||||||
tags: string[] = this.value.trim() !== "" ? this.value.trim().split(" ") : [];
|
tags: string[] = this.value.trim() !== '' ? this.value.trim().split(' ') : [];
|
||||||
buffer: string = "";
|
buffer = '';
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
bus.$on("add-tag", (tag: string) => this.tags.indexOf(tag) === -1 && this.tags.push(tag));
|
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));
|
bus.$on('resend-tag', () => bus.$emit('change-tag', this.tags));
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyDown(event: KeyboardEvent) {
|
onKeyDown(event: KeyboardEvent) {
|
||||||
if (this.buffer.trim() !== "") {
|
if (this.buffer.trim() !== '') {
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case 'Tab':
|
case 'Tab':
|
||||||
case 'Enter':
|
case 'Enter':
|
||||||
case ' ':
|
case ' ':
|
||||||
if ((event as any).isComposing !== true) {
|
if ((event as any).isComposing !== true) {
|
||||||
this.tags.push(this.buffer.trim());
|
this.tags.push(this.buffer.trim());
|
||||||
this.buffer = "";
|
this.buffer = '';
|
||||||
}
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
case 'Unidentified':
|
||||||
|
// 実際にテキストボックスに入力されている文字を見に行く (フォールバック処理)
|
||||||
|
if (event.srcElement && (event.srcElement as HTMLInputElement).value.slice(-1) == ' ') {
|
||||||
|
this.tags.push(this.buffer.trim());
|
||||||
|
this.buffer = '';
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
break;
|
}
|
||||||
case 'Unidentified':
|
break;
|
||||||
// 実際にテキストボックスに入力されている文字を見に行く (フォールバック処理)
|
|
||||||
if (event.srcElement && (event.srcElement as HTMLInputElement).value.slice(-1) == ' ') {
|
|
||||||
this.tags.push(this.buffer.trim());
|
|
||||||
this.buffer = "";
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else if (event.key === "Enter") {
|
|
||||||
// 誤爆防止
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
}
|
||||||
}
|
} else if (event.key === 'Enter') {
|
||||||
|
// 誤爆防止
|
||||||
removeTag(index: number) {
|
event.preventDefault();
|
||||||
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(" ");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeTag(index: number) {
|
||||||
|
this.tags.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Watch('tags')
|
||||||
|
onTagsChanged() {
|
||||||
|
bus.$emit('change-tag', this.tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
get containerClass() {
|
||||||
|
return {
|
||||||
|
'form-control': true,
|
||||||
|
'h-auto': true,
|
||||||
|
'is-invalid': this.isInvalid,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
get tagValue(): string {
|
||||||
|
return this.tags.join(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.tag-item {
|
.tag-item {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-input {
|
.tag-input {
|
||||||
border: 0;
|
border: 0;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
38
resources/assets/js/home.js
vendored
38
resources/assets/js/home.js
vendored
@ -1,38 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
47
resources/assets/js/home.ts
Normal file
47
resources/assets/js/home.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import * as Chart from 'chart.js';
|
||||||
|
|
||||||
|
const graph = document.getElementById('global-count-graph') as HTMLCanvasElement;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
const labels = JSON.parse(document.getElementById('global-count-labels')!.textContent as string);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
const data = JSON.parse(document.getElementById('global-count-data')!.textContent as string);
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
5
resources/assets/js/setting/privacy.js
vendored
5
resources/assets/js/setting/privacy.js
vendored
@ -1,5 +0,0 @@
|
|||||||
$('#protected').on('change', function () {
|
|
||||||
if (!$(this).prop('checked')) {
|
|
||||||
alert('チェックイン履歴を公開に切り替えると、個別に非公開設定されているものを除いた全てのチェックインが誰でも閲覧できるようになります。\nご注意ください。');
|
|
||||||
}
|
|
||||||
});
|
|
7
resources/assets/js/setting/privacy.ts
Normal file
7
resources/assets/js/setting/privacy.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
$('#protected').on('change', function () {
|
||||||
|
if (!$(this).prop('checked')) {
|
||||||
|
alert(
|
||||||
|
'チェックイン履歴を公開に切り替えると、個別に非公開設定されているものを除いた全てのチェックインが誰でも閲覧できるようになります。\nご注意ください。'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
72
resources/assets/js/tissue.js
vendored
72
resources/assets/js/tissue.js
vendored
@ -1,72 +0,0 @@
|
|||||||
(function ($) {
|
|
||||||
|
|
||||||
$.fn.linkCard = function (options) {
|
|
||||||
var settings = $.extend({
|
|
||||||
endpoint: '/api/checkin/card'
|
|
||||||
}, options);
|
|
||||||
|
|
||||||
return this.each(function () {
|
|
||||||
var $this = $(this);
|
|
||||||
$.ajax({
|
|
||||||
url: settings.endpoint,
|
|
||||||
method: 'get',
|
|
||||||
type: 'json',
|
|
||||||
data: {
|
|
||||||
url: $this.find('a').attr('href')
|
|
||||||
}
|
|
||||||
}).then(function (data) {
|
|
||||||
var $metaColumn = $this.find('.col-12:last-of-type');
|
|
||||||
var $imageColumn = $this.find('.col-12:first-of-type');
|
|
||||||
var $title = $this.find('.card-title');
|
|
||||||
var $desc = $this.find('.card-text');
|
|
||||||
var $image = $imageColumn.find('img');
|
|
||||||
|
|
||||||
if (data.title === '') {
|
|
||||||
$title.hide();
|
|
||||||
} else {
|
|
||||||
$title.text(data.title);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.description === '') {
|
|
||||||
$desc.hide();
|
|
||||||
} else {
|
|
||||||
$desc.text(data.description);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.image === '') {
|
|
||||||
$imageColumn.hide();
|
|
||||||
$metaColumn.removeClass('col-md-6');
|
|
||||||
} else {
|
|
||||||
$image.attr('src', data.image);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.title !== '' || data.description !== '' || data.image !== '') {
|
|
||||||
$this.removeClass('d-none');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$.fn.pageSelector = function () {
|
|
||||||
return this.on('change', function () {
|
|
||||||
location.href = $(this).find(':selected').data('href');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$.fn.deleteCheckinModal = function () {
|
|
||||||
return this.each(function () {
|
|
||||||
$(this).on('show.bs.modal', function (event) {
|
|
||||||
var target = $(event.relatedTarget);
|
|
||||||
var modal = $(this);
|
|
||||||
modal.find('.modal-body .date-label').text(target.data('date'));
|
|
||||||
modal.data('id', target.data('id'));
|
|
||||||
}).find('.btn-danger').on('click', function (event) {
|
|
||||||
var modal = $('#deleteCheckinModal');
|
|
||||||
var form = modal.find('form');
|
|
||||||
form.attr('action', form.attr('action').replace('@', modal.data('id')));
|
|
||||||
form.submit();
|
|
||||||
})
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
})(jQuery);
|
|
77
resources/assets/js/tissue.ts
Normal file
77
resources/assets/js/tissue.ts
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
(function ($) {
|
||||||
|
$.fn.linkCard = function (options) {
|
||||||
|
const settings = $.extend(
|
||||||
|
{
|
||||||
|
endpoint: '/api/checkin/card',
|
||||||
|
},
|
||||||
|
options
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.each(function () {
|
||||||
|
const $this = $(this);
|
||||||
|
$.ajax({
|
||||||
|
url: settings.endpoint,
|
||||||
|
method: 'get',
|
||||||
|
type: 'json',
|
||||||
|
data: {
|
||||||
|
url: $this.find('a').attr('href'),
|
||||||
|
},
|
||||||
|
}).then(function (data) {
|
||||||
|
const $metaColumn = $this.find('.col-12:last-of-type');
|
||||||
|
const $imageColumn = $this.find('.col-12:first-of-type');
|
||||||
|
const $title = $this.find('.card-title');
|
||||||
|
const $desc = $this.find('.card-text');
|
||||||
|
const $image = $imageColumn.find('img');
|
||||||
|
|
||||||
|
if (data.title === '') {
|
||||||
|
$title.hide();
|
||||||
|
} else {
|
||||||
|
$title.text(data.title);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.description === '') {
|
||||||
|
$desc.hide();
|
||||||
|
} else {
|
||||||
|
$desc.text(data.description);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.image === '') {
|
||||||
|
$imageColumn.hide();
|
||||||
|
$metaColumn.removeClass('col-md-6');
|
||||||
|
} else {
|
||||||
|
$image.attr('src', data.image);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.title !== '' || data.description !== '' || data.image !== '') {
|
||||||
|
$this.removeClass('d-none');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.pageSelector = function () {
|
||||||
|
return this.on('change', function () {
|
||||||
|
location.href = $(this).find(':selected').data('href');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.deleteCheckinModal = function () {
|
||||||
|
return this.each(function () {
|
||||||
|
$(this)
|
||||||
|
.on('show.bs.modal', function (event) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
const target = $(event.relatedTarget!);
|
||||||
|
const modal = $(this);
|
||||||
|
modal.find('.modal-body .date-label').text(target.data('date'));
|
||||||
|
modal.data('id', target.data('id'));
|
||||||
|
})
|
||||||
|
.find('.btn-danger')
|
||||||
|
.on('click', function (_event) {
|
||||||
|
const modal = $('#deleteCheckinModal');
|
||||||
|
const form = modal.find('form');
|
||||||
|
form.attr('action', form.attr('action')?.replace('@', modal.data('id')) || null);
|
||||||
|
form.submit();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
})(jQuery);
|
@ -1,4 +1,5 @@
|
|||||||
import CalHeatMap from 'cal-heatmap';
|
import * as CalHeatMap from 'cal-heatmap';
|
||||||
|
import { subMonths } from 'date-fns';
|
||||||
|
|
||||||
if (document.getElementById('cal-heatmap')) {
|
if (document.getElementById('cal-heatmap')) {
|
||||||
new CalHeatMap().init({
|
new CalHeatMap().init({
|
||||||
@ -7,9 +8,10 @@ if (document.getElementById('cal-heatmap')) {
|
|||||||
subDomain: 'day',
|
subDomain: 'day',
|
||||||
domainLabelFormat: '%Y/%m',
|
domainLabelFormat: '%Y/%m',
|
||||||
weekStartOnMonday: false,
|
weekStartOnMonday: false,
|
||||||
start: new Date().setMonth(new Date().getMonth() - 9),
|
start: subMonths(new Date(), 9),
|
||||||
range: 10,
|
range: 10,
|
||||||
data: JSON.parse(document.getElementById('count-by-day').textContent),
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
legend: [1, 2, 3, 4]
|
data: JSON.parse(document.getElementById('count-by-day')!.textContent as string),
|
||||||
|
legend: [1, 2, 3, 4],
|
||||||
});
|
});
|
||||||
}
|
}
|
124
resources/assets/js/user/stats.js
vendored
124
resources/assets/js/user/stats.js
vendored
@ -1,124 +0,0 @@
|
|||||||
import CalHeatMap from 'cal-heatmap';
|
|
||||||
import Chart from 'chart.js';
|
|
||||||
import {addMonths, format} from 'date-fns';
|
|
||||||
|
|
||||||
const graphData = JSON.parse(document.getElementById('graph-data').textContent);
|
|
||||||
|
|
||||||
function createLineGraph(id, labels, data) {
|
|
||||||
const context = document.getElementById(id).getContext('2d');
|
|
||||||
return new Chart(context, {
|
|
||||||
type: 'line',
|
|
||||||
data: {
|
|
||||||
labels: labels,
|
|
||||||
datasets: [{
|
|
||||||
data: data,
|
|
||||||
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
|
||||||
borderColor: 'rgba(255, 99, 132, 1)',
|
|
||||||
borderWidth: 1
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
legend: {
|
|
||||||
display: false
|
|
||||||
},
|
|
||||||
elements: {
|
|
||||||
line: {
|
|
||||||
tension: 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
yAxes: [{
|
|
||||||
ticks: {
|
|
||||||
beginAtZero: true
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
tooltips: {
|
|
||||||
mode: 'index',
|
|
||||||
intersect: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function createBarGraph(id, labels, data) {
|
|
||||||
const context = document.getElementById(id).getContext('2d');
|
|
||||||
new Chart(context, {
|
|
||||||
type: 'bar',
|
|
||||||
data: {
|
|
||||||
labels: labels,
|
|
||||||
datasets: [{
|
|
||||||
data: data,
|
|
||||||
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
|
||||||
borderColor: 'rgba(255, 99, 132, 1)',
|
|
||||||
borderWidth: 1
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
legend: {
|
|
||||||
display: false
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
yAxes: [{
|
|
||||||
ticks: {
|
|
||||||
beginAtZero: true
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
tooltips: {
|
|
||||||
mode: 'index',
|
|
||||||
intersect: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Date} from
|
|
||||||
*/
|
|
||||||
function createMonthlyGraphData(from) {
|
|
||||||
const keys = [];
|
|
||||||
const values = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < 12; i++) {
|
|
||||||
const current = addMonths(from, i);
|
|
||||||
const yearAndMonth = format(current, 'YYYY/MM');
|
|
||||||
keys.push(yearAndMonth);
|
|
||||||
values.push(graphData.monthlySum[yearAndMonth] || 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {keys, values};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCurrentYear() {
|
|
||||||
const year = location.pathname.split('/').pop();
|
|
||||||
return /^(20[0-9]{2})$/.test(year) ? year : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (document.getElementById('cal-heatmap')) {
|
|
||||||
new CalHeatMap().init({
|
|
||||||
itemSelector: '#cal-heatmap',
|
|
||||||
domain: 'month',
|
|
||||||
subDomain: 'day',
|
|
||||||
domainLabelFormat: '%Y/%m',
|
|
||||||
weekStartOnMonday: false,
|
|
||||||
start: new Date(getCurrentYear(), 0, 1, 0, 0, 0, 0),
|
|
||||||
range: 12,
|
|
||||||
data: graphData.dailySum,
|
|
||||||
legend: [1, 2, 3, 4]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (document.getElementById('monthly-graph')) {
|
|
||||||
const {keys: monthlyKey, values: monthlySum} = createMonthlyGraphData(new Date(getCurrentYear(), 0, 1, 0, 0, 0, 0));
|
|
||||||
createLineGraph('monthly-graph', monthlyKey, monthlySum);
|
|
||||||
}
|
|
||||||
if (document.getElementById('yearly-graph')) {
|
|
||||||
createLineGraph('yearly-graph', graphData.yearlyKey, graphData.yearlySum);
|
|
||||||
}
|
|
||||||
if (document.getElementById('hourly-graph')) {
|
|
||||||
createBarGraph('hourly-graph', graphData.hourlyKey, graphData.hourlySum);
|
|
||||||
}
|
|
||||||
if (document.getElementById('dow-graph')) {
|
|
||||||
createBarGraph('dow-graph', ['日', '月', '火', '水', '木', '金', '土'], graphData.dowSum);
|
|
||||||
}
|
|
138
resources/assets/js/user/stats.ts
Normal file
138
resources/assets/js/user/stats.ts
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import * as CalHeatMap from 'cal-heatmap';
|
||||||
|
import * as Chart from 'chart.js';
|
||||||
|
import { addMonths, format } from 'date-fns';
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
const graphData = JSON.parse(document.getElementById('graph-data')!.textContent as string);
|
||||||
|
|
||||||
|
function createLineGraph(id: string, labels: string[], data: any) {
|
||||||
|
const context = (document.getElementById(id) as HTMLCanvasElement).getContext('2d');
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
return new Chart(context!, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: labels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
data: data,
|
||||||
|
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
||||||
|
borderColor: 'rgba(255, 99, 132, 1)',
|
||||||
|
borderWidth: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
legend: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
line: {
|
||||||
|
tension: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
yAxes: [
|
||||||
|
{
|
||||||
|
ticks: {
|
||||||
|
beginAtZero: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
tooltips: {
|
||||||
|
mode: 'index',
|
||||||
|
intersect: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createBarGraph(id: string, labels: string[], data: any) {
|
||||||
|
const context = (document.getElementById(id) as HTMLCanvasElement).getContext('2d');
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
new Chart(context!, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: labels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
data: data,
|
||||||
|
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
||||||
|
borderColor: 'rgba(255, 99, 132, 1)',
|
||||||
|
borderWidth: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
legend: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
yAxes: [
|
||||||
|
{
|
||||||
|
ticks: {
|
||||||
|
beginAtZero: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
tooltips: {
|
||||||
|
mode: 'index',
|
||||||
|
intersect: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMonthlyGraphData(from: Date) {
|
||||||
|
const keys = [];
|
||||||
|
const values = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < 12; i++) {
|
||||||
|
const current = addMonths(from, i);
|
||||||
|
const yearAndMonth = format(current, 'YYYY/MM');
|
||||||
|
keys.push(yearAndMonth);
|
||||||
|
values.push(graphData.monthlySum[yearAndMonth] || 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { keys, values };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentYear(): number {
|
||||||
|
const year = location.pathname.split('/').pop() || '';
|
||||||
|
if (/^(20[0-9]{2})$/.test(year)) {
|
||||||
|
return parseInt(year, 10);
|
||||||
|
} else {
|
||||||
|
throw 'Invalid year';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.getElementById('cal-heatmap')) {
|
||||||
|
new CalHeatMap().init({
|
||||||
|
itemSelector: '#cal-heatmap',
|
||||||
|
domain: 'month',
|
||||||
|
subDomain: 'day',
|
||||||
|
domainLabelFormat: '%Y/%m',
|
||||||
|
weekStartOnMonday: false,
|
||||||
|
start: new Date(getCurrentYear(), 0, 1, 0, 0, 0, 0),
|
||||||
|
range: 12,
|
||||||
|
data: graphData.dailySum,
|
||||||
|
legend: [1, 2, 3, 4],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.getElementById('monthly-graph')) {
|
||||||
|
const { keys: monthlyKey, values: monthlySum } = createMonthlyGraphData(
|
||||||
|
new Date(getCurrentYear(), 0, 1, 0, 0, 0, 0)
|
||||||
|
);
|
||||||
|
createLineGraph('monthly-graph', monthlyKey, monthlySum);
|
||||||
|
}
|
||||||
|
if (document.getElementById('yearly-graph')) {
|
||||||
|
createLineGraph('yearly-graph', graphData.yearlyKey, graphData.yearlySum);
|
||||||
|
}
|
||||||
|
if (document.getElementById('hourly-graph')) {
|
||||||
|
createBarGraph('hourly-graph', graphData.hourlyKey, graphData.hourlySum);
|
||||||
|
}
|
||||||
|
if (document.getElementById('dow-graph')) {
|
||||||
|
createBarGraph('dow-graph', ['日', '月', '火', '水', '木', '金', '土'], graphData.dowSum);
|
||||||
|
}
|
4
resources/assets/js/vue-shims.d.ts
vendored
4
resources/assets/js/vue-shims.d.ts
vendored
@ -1,4 +0,0 @@
|
|||||||
declare module "*.vue" {
|
|
||||||
import Vue from "vue";
|
|
||||||
export default Vue;
|
|
||||||
}
|
|
19
resources/lang/ja/auth.php
Normal file
19
resources/lang/ja/auth.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Authentication Language Lines
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The following language lines are used during authentication for various
|
||||||
|
| messages that we need to display to the user. You are free to modify
|
||||||
|
| these language lines according to your application's requirements.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'failed' => 'ログイン情報が一致しません。',
|
||||||
|
'throttle' => 'ログイン試行が多すぎます。 :seconds 秒後にやりなおしてください。',
|
||||||
|
|
||||||
|
];
|
25
webpack.mix.js
vendored
25
webpack.mix.js
vendored
@ -12,22 +12,27 @@ require('laravel-mix-bundle-analyzer')
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
mix.js('resources/assets/js/app.js', 'public/js')
|
mix.ts('resources/assets/js/app.ts', 'public/js')
|
||||||
.js('resources/assets/js/home.js', 'public/js')
|
.ts('resources/assets/js/home.ts', 'public/js')
|
||||||
.js('resources/assets/js/user/profile.js', 'public/js/user')
|
.ts('resources/assets/js/user/profile.ts', 'public/js/user')
|
||||||
.js('resources/assets/js/user/stats.js', 'public/js/user')
|
.ts('resources/assets/js/user/stats.ts', 'public/js/user')
|
||||||
.js('resources/assets/js/setting/privacy.js', 'public/js/setting')
|
.ts('resources/assets/js/setting/privacy.ts', 'public/js/setting')
|
||||||
.js('resources/assets/js/setting/import.js', 'public/js/setting')
|
.ts('resources/assets/js/setting/import.ts', 'public/js/setting')
|
||||||
.js('resources/assets/js/setting/deactivate.js', 'public/js/setting')
|
.ts('resources/assets/js/setting/deactivate.ts', 'public/js/setting')
|
||||||
.ts('resources/assets/js/checkin.ts', 'public/js')
|
.ts('resources/assets/js/checkin.ts', 'public/js')
|
||||||
.sass('resources/assets/sass/app.scss', 'public/css')
|
.sass('resources/assets/sass/app.scss', 'public/css')
|
||||||
.autoload({
|
.autoload({
|
||||||
'jquery': ['$', 'jQuery', 'window.jQuery']
|
'jquery': ['$', 'jQuery', 'window.jQuery']
|
||||||
})
|
})
|
||||||
.extract(['jquery', 'bootstrap'])
|
.extract(['jquery', 'bootstrap'])
|
||||||
.extract(['chart.js', 'chartjs-color', 'color-name', 'moment'], 'public/js/vendor/chart')
|
.extract(['chart.js', 'chartjs-color', 'color-name', 'moment', 'cal-heatmap', 'd3'], 'public/js/vendor/chart')
|
||||||
.version();
|
.version()
|
||||||
|
.webpackConfig(webpack => ({
|
||||||
|
externals: {
|
||||||
|
moment: 'moment'
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
if (process.argv.includes('-a')) {
|
if (process.argv.includes('-a')) {
|
||||||
mix.bundleAnalyzer({analyzerMode: 'static'});
|
mix.bundleAnalyzer({ analyzerMode: 'static' });
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user