@@ -105,6 +105,12 @@ jobs:
 | 
			
		||||
          command: yarn run stylelint
 | 
			
		||||
          when: always
 | 
			
		||||
 | 
			
		||||
      # Run eslint
 | 
			
		||||
      - run:
 | 
			
		||||
          name: eslint
 | 
			
		||||
          command: yarn run eslint
 | 
			
		||||
          when: always
 | 
			
		||||
 | 
			
		||||
      # Run unit test
 | 
			
		||||
      - run:
 | 
			
		||||
          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/storage
 | 
			
		||||
/public/mix-manifest.json
 | 
			
		||||
/public/report.html
 | 
			
		||||
/storage/*.key
 | 
			
		||||
/vendor
 | 
			
		||||
/.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;
 | 
			
		||||
 | 
			
		||||
use GuzzleHttp\Client;
 | 
			
		||||
use GuzzleHttp\Cookie\CookieJar;
 | 
			
		||||
use Symfony\Component\DomCrawler\Crawler;
 | 
			
		||||
 | 
			
		||||
class FanzaResolver implements Resolver
 | 
			
		||||
@@ -43,7 +44,9 @@ class FanzaResolver implements Resolver
 | 
			
		||||
 | 
			
		||||
    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();
 | 
			
		||||
        $crawler = new Crawler($html);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,7 @@ class MetadataResolver implements Resolver
 | 
			
		||||
        '~dmm\.co\.jp/~' => FanzaResolver::class,
 | 
			
		||||
        '~www\.patreon\.com/~' => PatreonResolver::class,
 | 
			
		||||
        '~www\.deviantart\.com/.*/art/.*~' => DeviantArtResolver::class,
 | 
			
		||||
        '~\.syosetu\.com/n\d+[a-z]{2,}~' => NarouResolver::class,
 | 
			
		||||
        '~\.syosetu\.com/n\d+[a-z]+~' => NarouResolver::class,
 | 
			
		||||
        '~ci-en\.(jp|net|dlsite\.com)/creator/\d+/article/\d+~' => CienResolver::class,
 | 
			
		||||
        '~www\.plurk\.com\/p\/.*~' => PlurkResolver::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",
 | 
			
		||||
    "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",
 | 
			
		||||
    "eslint": "eslint --ext .js,.ts,.vue resources/",
 | 
			
		||||
    "stylelint": "stylelint resources/assets/sass/**/*"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@types/bootstrap": "^4.5.0",
 | 
			
		||||
    "@types/cal-heatmap": "^3.3.10",
 | 
			
		||||
    "@types/chart.js": "^2.9.22",
 | 
			
		||||
    "@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",
 | 
			
		||||
    "chart.js": "^2.7.1",
 | 
			
		||||
    "cross-env": "^5.2.0",
 | 
			
		||||
    "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",
 | 
			
		||||
    "jquery": "^3.5.0",
 | 
			
		||||
    "js-cookie": "^2.2.0",
 | 
			
		||||
@@ -25,16 +36,17 @@
 | 
			
		||||
    "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",
 | 
			
		||||
    "prettier": "^2.0.5",
 | 
			
		||||
    "resolve-url-loader": "^3.1.1",
 | 
			
		||||
    "sass": "^1.26.8",
 | 
			
		||||
    "sass-loader": "^7.1.0",
 | 
			
		||||
    "stylelint": "^9.10.1",
 | 
			
		||||
    "stylelint-config-recess-order": "^2.0.1",
 | 
			
		||||
    "stylelint-config-recess-order": "^2.0.4",
 | 
			
		||||
    "ts-loader": "^6.0.1",
 | 
			
		||||
    "typescript": "^3.4.5",
 | 
			
		||||
    "vue": "^2.6.10",
 | 
			
		||||
    "vue-class-component": "^7.1.0",
 | 
			
		||||
    "vue-property-decorator": "^8.1.1",
 | 
			
		||||
    "vue-property-decorator": "^9.0.0",
 | 
			
		||||
    "vue-template-compiler": "^2.6.10"
 | 
			
		||||
  },
 | 
			
		||||
  "stylelint": {
 | 
			
		||||
@@ -50,6 +62,10 @@
 | 
			
		||||
      "stylelint --fix",
 | 
			
		||||
      "git add"
 | 
			
		||||
    ],
 | 
			
		||||
    "*.{ts,js,vue}" : [
 | 
			
		||||
      "eslint --fix",
 | 
			
		||||
      "git add"
 | 
			
		||||
    ],
 | 
			
		||||
    "*.php": [
 | 
			
		||||
      "composer fix",
 | 
			
		||||
      "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');
 | 
			
		||||
 | 
			
		||||
@@ -7,10 +8,10 @@ $(() => {
 | 
			
		||||
        $('body').removeClass('tis-need-agecheck');
 | 
			
		||||
    } else {
 | 
			
		||||
        $('#ageCheckModal')
 | 
			
		||||
            .modal({backdrop: 'static'})
 | 
			
		||||
            .modal({ backdrop: 'static' })
 | 
			
		||||
            .on('hide.bs.modal', function () {
 | 
			
		||||
                $('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);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $(document).on('click', '[data-href]', function (event) {
 | 
			
		||||
    $(document).on('click', '[data-href]', function (_event) {
 | 
			
		||||
        location.href = $(this).data('href');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@@ -40,7 +41,7 @@ $(() => {
 | 
			
		||||
        const isLiked = $this.data('liked');
 | 
			
		||||
 | 
			
		||||
        if (isLiked) {
 | 
			
		||||
            const callback = (data) => {
 | 
			
		||||
            const callback = (data: any) => {
 | 
			
		||||
                $this.data('liked', false);
 | 
			
		||||
                $this.find('.oi-heart').removeClass('text-danger');
 | 
			
		||||
 | 
			
		||||
@@ -51,10 +52,10 @@ $(() => {
 | 
			
		||||
            $.ajax({
 | 
			
		||||
                url: '/api/likes/' + encodeURIComponent(targetId),
 | 
			
		||||
                method: 'delete',
 | 
			
		||||
                type: 'json'
 | 
			
		||||
                type: 'json',
 | 
			
		||||
            })
 | 
			
		||||
                .then(callback)
 | 
			
		||||
                .catch(function (xhr) {
 | 
			
		||||
                .catch(function (xhr: jqXHR) {
 | 
			
		||||
                    if (xhr.status === 404) {
 | 
			
		||||
                        callback(JSON.parse(xhr.responseText));
 | 
			
		||||
                        return;
 | 
			
		||||
@@ -64,7 +65,7 @@ $(() => {
 | 
			
		||||
                    alert('いいねを解除できませんでした。');
 | 
			
		||||
                });
 | 
			
		||||
        } else {
 | 
			
		||||
            const callback = (data) => {
 | 
			
		||||
            const callback = (data: any) => {
 | 
			
		||||
                $this.data('liked', true);
 | 
			
		||||
                $this.find('.oi-heart').addClass('text-danger');
 | 
			
		||||
 | 
			
		||||
@@ -77,11 +78,11 @@ $(() => {
 | 
			
		||||
                method: 'post',
 | 
			
		||||
                type: 'json',
 | 
			
		||||
                data: {
 | 
			
		||||
                    id: targetId
 | 
			
		||||
                }
 | 
			
		||||
                    id: targetId,
 | 
			
		||||
                },
 | 
			
		||||
            })
 | 
			
		||||
                .then(callback)
 | 
			
		||||
                .catch(function (xhr) {
 | 
			
		||||
                .catch(function (xhr: jqXHR) {
 | 
			
		||||
                    if (xhr.status === 409) {
 | 
			
		||||
                        callback(JSON.parse(xhr.responseText));
 | 
			
		||||
                        return;
 | 
			
		||||
@@ -96,9 +97,9 @@ $(() => {
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $(document).on('click', '.card-spoiler-overlay', function (event) {
 | 
			
		||||
    $(document).on('click', '.card-spoiler-overlay', function (_event) {
 | 
			
		||||
        const $this = $(this);
 | 
			
		||||
        $this.siblings(".card-link").removeClass("card-spoiler");
 | 
			
		||||
        $this.siblings('.card-link').removeClass('card-spoiler');
 | 
			
		||||
        $this.remove();
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -2,16 +2,16 @@
 | 
			
		||||
import './tissue';
 | 
			
		||||
 | 
			
		||||
// 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) {
 | 
			
		||||
    console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
$.ajaxSetup({
 | 
			
		||||
} else {
 | 
			
		||||
    $.ajaxSetup({
 | 
			
		||||
        headers: {
 | 
			
		||||
        'X-CSRF-TOKEN': token.content
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
            'X-CSRF-TOKEN': token.content,
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Bootstrap
 | 
			
		||||
import 'bootstrap';
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import TagInput from "./components/TagInput.vue";
 | 
			
		||||
import TagInput from './components/TagInput.vue';
 | 
			
		||||
import MetadataPreview from './components/MetadataPreview.vue';
 | 
			
		||||
 | 
			
		||||
export const bus = new Vue({name: "EventBus"});
 | 
			
		||||
export const bus = new Vue({ name: 'EventBus' });
 | 
			
		||||
 | 
			
		||||
export enum MetadataLoadState {
 | 
			
		||||
    Inactive,
 | 
			
		||||
@@ -19,11 +19,11 @@ new Vue({
 | 
			
		||||
    },
 | 
			
		||||
    components: {
 | 
			
		||||
        TagInput,
 | 
			
		||||
        MetadataPreview
 | 
			
		||||
        MetadataPreview,
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        // オカズリンクにURLがセットされている場合は、すぐにメタデータを取得する
 | 
			
		||||
        const linkInput = this.$el.querySelector<HTMLInputElement>("#link");
 | 
			
		||||
        const linkInput = this.$el.querySelector<HTMLInputElement>('#link');
 | 
			
		||||
        if (linkInput && /^https?:\/\//.test(linkInput.value)) {
 | 
			
		||||
            this.fetchMetadata(linkInput.value);
 | 
			
		||||
        }
 | 
			
		||||
@@ -52,15 +52,17 @@ new Vue({
 | 
			
		||||
                method: 'get',
 | 
			
		||||
                type: 'json',
 | 
			
		||||
                data: {
 | 
			
		||||
                    url
 | 
			
		||||
                }
 | 
			
		||||
            }).then(data => {
 | 
			
		||||
                    url,
 | 
			
		||||
                },
 | 
			
		||||
            })
 | 
			
		||||
                .then((data) => {
 | 
			
		||||
                    this.metadata = data;
 | 
			
		||||
                    this.metadataLoadState = MetadataLoadState.Success;
 | 
			
		||||
            }).catch(e => {
 | 
			
		||||
                })
 | 
			
		||||
                .catch((_e) => {
 | 
			
		||||
                    this.metadata = null;
 | 
			
		||||
                    this.metadataLoadState = MetadataLoadState.Failed;
 | 
			
		||||
                });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -5,23 +5,34 @@
 | 
			
		||||
                <div v-if="state === MetadataLoadState.Loading" class="row no-gutters">
 | 
			
		||||
                    <div class="col-12">
 | 
			
		||||
                        <div class="card-body">
 | 
			
		||||
                            <h6 class="card-title text-center font-weight-bold text-info" style="font-size: small;"><span class="oi oi-loop-circular"></span> オカズの情報を読み込んでいます…</h6>
 | 
			
		||||
                            <h6 class="card-title text-center font-weight-bold text-info" style="font-size: small;">
 | 
			
		||||
                                <span class="oi oi-loop-circular"></span> オカズの情報を読み込んでいます…
 | 
			
		||||
                            </h6>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div v-else-if="state === MetadataLoadState.Success" class="row no-gutters">
 | 
			
		||||
                    <div v-if="hasImage" class="col-4 justify-content-center align-items-center">
 | 
			
		||||
                        <img :src="metadata.image" alt="Thumbnail" class="w-100 bg-secondary">
 | 
			
		||||
                        <img :src="metadata.image" alt="Thumbnail" class="w-100 bg-secondary" />
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div :class="descClasses">
 | 
			
		||||
                        <div class="card-body">
 | 
			
		||||
                            <h6 class="card-title font-weight-bold" style="font-size: small;">{{ metadata.title }}</h6>
 | 
			
		||||
                            <template v-if="suggestions.length > 0">
 | 
			
		||||
                                <p class="card-text mb-2" style="font-size: small;">タグ候補<br><span class="text-secondary">(クリックするとタグ入力欄にコピーできます)</span></p>
 | 
			
		||||
                                <p class="card-text mb-2" style="font-size: small;">
 | 
			
		||||
                                    タグ候補<br /><span class="text-secondary"
 | 
			
		||||
                                        >(クリックするとタグ入力欄にコピーできます)</span
 | 
			
		||||
                                    >
 | 
			
		||||
                                </p>
 | 
			
		||||
                                <ul class="list-inline d-inline">
 | 
			
		||||
                                    <li v-for="tag in suggestions"
 | 
			
		||||
                                    <li
 | 
			
		||||
                                        v-for="tag in suggestions"
 | 
			
		||||
                                        :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>
 | 
			
		||||
                            </template>
 | 
			
		||||
                        </div>
 | 
			
		||||
@@ -30,7 +41,9 @@
 | 
			
		||||
                <div v-else class="row no-gutters">
 | 
			
		||||
                    <div class="col-12">
 | 
			
		||||
                        <div class="card-body">
 | 
			
		||||
                            <h6 class="card-title text-center font-weight-bold text-danger" style="font-size: small;"><span class="oi oi-circle-x"></span> オカズの情報を読み込めませんでした</h6>
 | 
			
		||||
                            <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>
 | 
			
		||||
@@ -40,27 +53,27 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
    import {Vue, Component, Prop} from "vue-property-decorator";
 | 
			
		||||
    import {bus, MetadataLoadState} from "../checkin";
 | 
			
		||||
import { Vue, Component, Prop } from 'vue-property-decorator';
 | 
			
		||||
import { bus, MetadataLoadState } from '../checkin';
 | 
			
		||||
 | 
			
		||||
    type Metadata = {
 | 
			
		||||
        url: string,
 | 
			
		||||
        title: string,
 | 
			
		||||
        description: string,
 | 
			
		||||
        image: string,
 | 
			
		||||
        expires_at: string | null,
 | 
			
		||||
type Metadata = {
 | 
			
		||||
    url: string;
 | 
			
		||||
    title: string;
 | 
			
		||||
    description: string;
 | 
			
		||||
    image: string;
 | 
			
		||||
    expires_at: string | null;
 | 
			
		||||
    tags: {
 | 
			
		||||
            name: string
 | 
			
		||||
        }[],
 | 
			
		||||
    };
 | 
			
		||||
        name: string;
 | 
			
		||||
    }[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
    type Suggestion = {
 | 
			
		||||
        name: string,
 | 
			
		||||
        used: boolean,
 | 
			
		||||
    }
 | 
			
		||||
type Suggestion = {
 | 
			
		||||
    name: string;
 | 
			
		||||
    used: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
    @Component
 | 
			
		||||
    export default class MetadataPreview extends Vue {
 | 
			
		||||
@Component
 | 
			
		||||
export default class MetadataPreview extends Vue {
 | 
			
		||||
    @Prop() readonly state!: MetadataLoadState;
 | 
			
		||||
    @Prop() readonly metadata!: Metadata | null;
 | 
			
		||||
 | 
			
		||||
@@ -69,22 +82,22 @@
 | 
			
		||||
 | 
			
		||||
    tags: string[] = [];
 | 
			
		||||
 | 
			
		||||
        created() {
 | 
			
		||||
            bus.$on("change-tag", (tags: string[]) => this.tags = tags);
 | 
			
		||||
            bus.$emit("resend-tag");
 | 
			
		||||
    created(): void {
 | 
			
		||||
        bus.$on('change-tag', (tags: string[]) => (this.tags = tags));
 | 
			
		||||
        bus.$emit('resend-tag');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        addTag(tag: string) {
 | 
			
		||||
            bus.$emit("add-tag", tag);
 | 
			
		||||
    addTag(tag: string): void {
 | 
			
		||||
        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,
 | 
			
		||||
            'list-inline-item': true,
 | 
			
		||||
            badge: true,
 | 
			
		||||
            'badge-primary': !s.used,
 | 
			
		||||
            'badge-secondary': s.used,
 | 
			
		||||
            'metadata-tag-item': true,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -93,36 +106,36 @@
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            return this.metadata.tags.map(t => {
 | 
			
		||||
        return this.metadata.tags.map((t) => {
 | 
			
		||||
            return {
 | 
			
		||||
                name: t.name,
 | 
			
		||||
                    used: this.tags.indexOf(t.name) !== -1
 | 
			
		||||
                used: this.tags.indexOf(t.name) !== -1,
 | 
			
		||||
            };
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get hasImage() {
 | 
			
		||||
            return this.metadata !== null && this.metadata.image !== ''
 | 
			
		||||
        return this.metadata !== null && this.metadata.image !== '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get descClasses() {
 | 
			
		||||
        return {
 | 
			
		||||
                "col-8": this.hasImage,
 | 
			
		||||
                "col-12": !this.hasImage,
 | 
			
		||||
            'col-8': this.hasImage,
 | 
			
		||||
            'col-12': !this.hasImage,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
    .link-card-mini {
 | 
			
		||||
.link-card-mini {
 | 
			
		||||
    $height: 150px;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
 | 
			
		||||
    .row > div:first-child {
 | 
			
		||||
        display: flex;
 | 
			
		||||
 | 
			
		||||
            &:not([display=none]) {
 | 
			
		||||
        &:not([display='none']) {
 | 
			
		||||
            min-height: $height;
 | 
			
		||||
 | 
			
		||||
            img {
 | 
			
		||||
@@ -134,10 +147,10 @@
 | 
			
		||||
    .card-text {
 | 
			
		||||
        white-space: pre-line;
 | 
			
		||||
    }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    .metadata-tag-item {
 | 
			
		||||
.metadata-tag-item {
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    user-select: none;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,48 +1,48 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <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">
 | 
			
		||||
            <li v-for="(tag, i) in tags"
 | 
			
		||||
            <li
 | 
			
		||||
                v-for="(tag, i) in tags"
 | 
			
		||||
                class="list-inline-item badge badge-primary tag-item"
 | 
			
		||||
                @click="removeTag(i)"><span class="oi oi-tag"></span> {{ tag }} | x</li>
 | 
			
		||||
                @click="removeTag(i)"
 | 
			
		||||
                :key="tag"
 | 
			
		||||
            >
 | 
			
		||||
                <span class="oi oi-tag"></span> {{ tag }} | x
 | 
			
		||||
            </li>
 | 
			
		||||
        </ul>
 | 
			
		||||
        <input :id="id"
 | 
			
		||||
               ref="input"
 | 
			
		||||
               type="text"
 | 
			
		||||
               class="tag-input"
 | 
			
		||||
               v-model="buffer"
 | 
			
		||||
               @keydown="onKeyDown">
 | 
			
		||||
        <input :id="id" ref="input" type="text" class="tag-input" v-model="buffer" @keydown="onKeyDown" />
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
    import {Vue, Component, Prop, Watch} from "vue-property-decorator";
 | 
			
		||||
    import {bus} from "../checkin";
 | 
			
		||||
import { Vue, Component, Prop, Watch } from 'vue-property-decorator';
 | 
			
		||||
import { bus } from '../checkin';
 | 
			
		||||
 | 
			
		||||
    @Component
 | 
			
		||||
    export default class TagInput extends Vue {
 | 
			
		||||
@Component
 | 
			
		||||
export default class TagInput extends Vue {
 | 
			
		||||
    @Prop(String) readonly id!: string;
 | 
			
		||||
    @Prop(String) readonly name!: string;
 | 
			
		||||
    @Prop(String) readonly value!: string;
 | 
			
		||||
    @Prop(Boolean) readonly isInvalid!: boolean;
 | 
			
		||||
 | 
			
		||||
        tags: string[] = this.value.trim() !== "" ? this.value.trim().split(" ") : [];
 | 
			
		||||
        buffer: string = "";
 | 
			
		||||
    tags: string[] = this.value.trim() !== '' ? this.value.trim().split(' ') : [];
 | 
			
		||||
    buffer = '';
 | 
			
		||||
 | 
			
		||||
    created() {
 | 
			
		||||
            bus.$on("add-tag", (tag: string) => this.tags.indexOf(tag) === -1 && this.tags.push(tag));
 | 
			
		||||
            bus.$on("resend-tag", () => bus.$emit("change-tag", this.tags));
 | 
			
		||||
        bus.$on('add-tag', (tag: string) => this.tags.indexOf(tag) === -1 && this.tags.push(tag));
 | 
			
		||||
        bus.$on('resend-tag', () => bus.$emit('change-tag', this.tags));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    onKeyDown(event: KeyboardEvent) {
 | 
			
		||||
            if (this.buffer.trim() !== "") {
 | 
			
		||||
        if (this.buffer.trim() !== '') {
 | 
			
		||||
            switch (event.key) {
 | 
			
		||||
                case 'Tab':
 | 
			
		||||
                case 'Enter':
 | 
			
		||||
                case ' ':
 | 
			
		||||
                    if ((event as any).isComposing !== true) {
 | 
			
		||||
                        this.tags.push(this.buffer.trim());
 | 
			
		||||
                            this.buffer = "";
 | 
			
		||||
                        this.buffer = '';
 | 
			
		||||
                    }
 | 
			
		||||
                    event.preventDefault();
 | 
			
		||||
                    break;
 | 
			
		||||
@@ -50,12 +50,12 @@
 | 
			
		||||
                    // 実際にテキストボックスに入力されている文字を見に行く (フォールバック処理)
 | 
			
		||||
                    if (event.srcElement && (event.srcElement as HTMLInputElement).value.slice(-1) == ' ') {
 | 
			
		||||
                        this.tags.push(this.buffer.trim());
 | 
			
		||||
                            this.buffer = "";
 | 
			
		||||
                        this.buffer = '';
 | 
			
		||||
                        event.preventDefault();
 | 
			
		||||
                    }
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
            } else if (event.key === "Enter") {
 | 
			
		||||
        } else if (event.key === 'Enter') {
 | 
			
		||||
            // 誤爆防止
 | 
			
		||||
            event.preventDefault();
 | 
			
		||||
        }
 | 
			
		||||
@@ -65,32 +65,32 @@
 | 
			
		||||
        this.tags.splice(index, 1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        @Watch("tags")
 | 
			
		||||
    @Watch('tags')
 | 
			
		||||
    onTagsChanged() {
 | 
			
		||||
            bus.$emit("change-tag", this.tags);
 | 
			
		||||
        bus.$emit('change-tag', this.tags);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        get containerClass(): object {
 | 
			
		||||
    get containerClass() {
 | 
			
		||||
        return {
 | 
			
		||||
                "form-control": true,
 | 
			
		||||
                "h-auto": true,
 | 
			
		||||
                "is-invalid": this.isInvalid
 | 
			
		||||
            'form-control': true,
 | 
			
		||||
            'h-auto': true,
 | 
			
		||||
            'is-invalid': this.isInvalid,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get tagValue(): string {
 | 
			
		||||
            return this.tags.join(" ");
 | 
			
		||||
        }
 | 
			
		||||
        return this.tags.join(' ');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
    .tag-item {
 | 
			
		||||
.tag-item {
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    .tag-input {
 | 
			
		||||
.tag-input {
 | 
			
		||||
    border: 0;
 | 
			
		||||
    outline: 0;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
</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')) {
 | 
			
		||||
    new CalHeatMap().init({
 | 
			
		||||
@@ -7,9 +8,10 @@ if (document.getElementById('cal-heatmap')) {
 | 
			
		||||
        subDomain: 'day',
 | 
			
		||||
        domainLabelFormat: '%Y/%m',
 | 
			
		||||
        weekStartOnMonday: false,
 | 
			
		||||
        start: new Date().setMonth(new Date().getMonth() - 9),
 | 
			
		||||
        start: subMonths(new Date(), 9),
 | 
			
		||||
        range: 10,
 | 
			
		||||
        data: JSON.parse(document.getElementById('count-by-day').textContent),
 | 
			
		||||
        legend: [1, 2, 3, 4]
 | 
			
		||||
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
 | 
			
		||||
        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')
 | 
			
		||||
    .js('resources/assets/js/home.js', 'public/js')
 | 
			
		||||
    .js('resources/assets/js/user/profile.js', 'public/js/user')
 | 
			
		||||
    .js('resources/assets/js/user/stats.js', 'public/js/user')
 | 
			
		||||
    .js('resources/assets/js/setting/privacy.js', 'public/js/setting')
 | 
			
		||||
    .js('resources/assets/js/setting/import.js', 'public/js/setting')
 | 
			
		||||
    .js('resources/assets/js/setting/deactivate.js', 'public/js/setting')
 | 
			
		||||
mix.ts('resources/assets/js/app.ts', 'public/js')
 | 
			
		||||
    .ts('resources/assets/js/home.ts', 'public/js')
 | 
			
		||||
    .ts('resources/assets/js/user/profile.ts', 'public/js/user')
 | 
			
		||||
    .ts('resources/assets/js/user/stats.ts', 'public/js/user')
 | 
			
		||||
    .ts('resources/assets/js/setting/privacy.ts', 'public/js/setting')
 | 
			
		||||
    .ts('resources/assets/js/setting/import.ts', 'public/js/setting')
 | 
			
		||||
    .ts('resources/assets/js/setting/deactivate.ts', 'public/js/setting')
 | 
			
		||||
    .ts('resources/assets/js/checkin.ts', '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();
 | 
			
		||||
    .extract(['chart.js', 'chartjs-color', 'color-name', 'moment', 'cal-heatmap', 'd3'], 'public/js/vendor/chart')
 | 
			
		||||
    .version()
 | 
			
		||||
    .webpackConfig(webpack => ({
 | 
			
		||||
        externals: {
 | 
			
		||||
            moment: 'moment'
 | 
			
		||||
        }
 | 
			
		||||
    }));
 | 
			
		||||
 | 
			
		||||
if (process.argv.includes('-a')) {
 | 
			
		||||
    mix.bundleAnalyzer({analyzerMode: 'static'});
 | 
			
		||||
    mix.bundleAnalyzer({ analyzerMode: 'static' });
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user