From 422891e237c12c508cc4c57ac200e49ba84572b7 Mon Sep 17 00:00:00 2001 From: shibafu Date: Thu, 6 Aug 2020 02:26:51 +0900 Subject: [PATCH 1/6] fetch wrapper --- package.json | 4 +- resources/assets/js/fetch.ts | 57 +++++++++++++++++++++++++++++ resources/assets/js/tissue.ts | 69 ++++++++++++++++++----------------- yarn.lock | 10 +++++ 4 files changed, 106 insertions(+), 34 deletions(-) create mode 100644 resources/assets/js/fetch.ts diff --git a/package.json b/package.json index 9f6056f..af8a402 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@types/chart.js": "^2.9.22", "@types/jquery": "^3.3.38", "@types/js-cookie": "^2.2.0", + "@types/qs": "^6.9.4", "@typescript-eslint/eslint-plugin": "^3.1.0", "@typescript-eslint/parser": "^3.1.0", "bootstrap": "^4.5.0", @@ -37,6 +38,7 @@ "open-iconic": "^1.1.1", "popper.js": "^1.14.7", "prettier": "^2.0.5", + "qs": "^6.9.4", "resolve-url-loader": "^3.1.1", "sass": "^1.26.8", "sass-loader": "^7.1.0", @@ -62,7 +64,7 @@ "stylelint --fix", "git add" ], - "*.{ts,js,vue}" : [ + "*.{ts,js,vue}": [ "eslint --fix", "git add" ], diff --git a/resources/assets/js/fetch.ts b/resources/assets/js/fetch.ts new file mode 100644 index 0000000..d683e62 --- /dev/null +++ b/resources/assets/js/fetch.ts @@ -0,0 +1,57 @@ +import { stringify } from 'qs'; + +const token = document.head.querySelector('meta[name="csrf-token"]'); +if (!token) { + console.error('CSRF token not found'); +} + +const headers = { + 'X-CSRF-TOKEN': token?.content ?? '', +}; + +type QueryParams = { [key: string]: string }; + +const joinParamsToPath = (path: string, params: QueryParams) => + Object.keys(params).length === 0 ? path : `${path}?${stringify(params)}`; + +const fetchWrapper = (path: string, options: RequestInit = {}) => + fetch(path, { + credentials: 'same-origin', + headers, + ...options, + }); + +const fetchWithJson = (path: string, body: any, options: RequestInit = {}) => + fetchWrapper(path, { + body: JSON.stringify(body), + headers: { 'Content-Type': 'application/json' }, + ...options, + }); + +const fetchWithForm = (path: string, body: any, options: RequestInit = {}) => + fetchWrapper(path, { + body: stringify(body), + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + ...options, + }); + +export const fetchGet = (path: string, params: QueryParams = {}, options: RequestInit = {}) => + fetchWrapper(joinParamsToPath(path, params), { method: 'GET', ...options }); + +export const fetchPostJson = (path: string, body: any, options: RequestInit = {}) => + fetchWithJson(path, body, { method: 'POST', ...options }); + +export const fetchPostForm = (path: string, body: any, options: RequestInit = {}) => + fetchWithForm(path, body, { method: 'POST', ...options }); + +export const fetchPutJson = (path: string, body: any, options: RequestInit = {}) => + fetchWithJson(path, body, { method: 'PUT', ...options }); + +export const fetchPutForm = (path: string, body: any, options: RequestInit = {}) => + fetchWithForm(path, body, { method: 'PUT', ...options }); + +export const fetchDeleteJson = (path: string, body: any, options: RequestInit = {}) => + fetchWithJson(path, body, { method: 'DELETE', ...options }); + +export const fetchDeleteForm = (path: string, body: any, options: RequestInit = {}) => + fetchWithForm(path, body, { method: 'DELETE', ...options }); diff --git a/resources/assets/js/tissue.ts b/resources/assets/js/tissue.ts index 1b7fe32..e059c75 100644 --- a/resources/assets/js/tissue.ts +++ b/resources/assets/js/tissue.ts @@ -1,3 +1,5 @@ +import { fetchGet } from './fetch'; + (function ($) { $.fn.linkCard = function (options) { const settings = $.extend( @@ -9,43 +11,44 @@ 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); - } + const url = $this.find('a').attr('href'); + if (!url) { + return; + } - if (data.description === '') { - $desc.hide(); - } else { - $desc.text(data.description); - } + fetchGet(settings.endpoint, { url }) + .then((response) => response.json()) + .then((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.image === '') { - $imageColumn.hide(); - $metaColumn.removeClass('col-md-6'); - } else { - $image.attr('src', data.image); - } + if (data.title === '') { + $title.hide(); + } else { + $title.text(data.title); + } - if (data.title !== '' || data.description !== '' || data.image !== '') { - $this.removeClass('d-none'); - } - }); + 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'); + } + }); }); }; diff --git a/yarn.lock b/yarn.lock index 96ceb48..95f4a21 100644 --- a/yarn.lock +++ b/yarn.lock @@ -849,6 +849,11 @@ resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8" integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw== +"@types/qs@^6.9.4": + version "6.9.4" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.4.tgz#a59e851c1ba16c0513ea123830dd639a0a15cb6a" + integrity sha512-+wYo+L6ZF6BMoEjtf8zB2esQsqdV6WsjRK/GP9WOgLPrq87PbNWgIxS76dS5uvl/QXtHGakZmwTznIfcPXcKlQ== + "@types/sizzle@*": version "2.3.2" resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.2.tgz#a811b8c18e2babab7d542b3365887ae2e4d9de47" @@ -6684,6 +6689,11 @@ qs@6.7.0: resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== +qs@^6.9.4: + version "6.9.4" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.4.tgz#9090b290d1f91728d3c22e54843ca44aea5ab687" + integrity sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ== + querystring-es3@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" From 126e44bcd937360e86bbf83a3c9cbe8ccd713a00 Mon Sep 17 00:00:00 2001 From: shibafu Date: Thu, 6 Aug 2020 09:08:37 +0900 Subject: [PATCH 2/6] =?UTF-8?q?=E4=BB=8A=E6=97=A5=E3=81=8B=E3=82=89jQuery.?= =?UTF-8?q?ajax=E7=A6=81=E6=AD=A2=E3=81=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc.js | 4 +++- package.json | 1 + yarn.lock | 5 +++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index 315b9d6..9c54f9b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -19,10 +19,12 @@ module.exports = { parser: '@typescript-eslint/parser', sourceType: 'module', }, - plugins: ['prettier', 'vue', '@typescript-eslint'], + plugins: ['prettier', 'vue', '@typescript-eslint', 'jquery'], rules: { '@typescript-eslint/explicit-module-boundary-types': 0, '@typescript-eslint/no-explicit-any': 0, '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], + 'jquery/no-ajax': 2, + 'jquery/no-ajax-events': 2, }, }; diff --git a/package.json b/package.json index af8a402..ccaf01b 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "date-fns": "^1.30.1", "eslint": "^7.2.0", "eslint-config-prettier": "^6.11.0", + "eslint-plugin-jquery": "^1.5.1", "eslint-plugin-prettier": "^3.1.4", "eslint-plugin-vue": "^6.2.2", "husky": "^1.3.1", diff --git a/yarn.lock b/yarn.lock index 95f4a21..34ff6b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3035,6 +3035,11 @@ eslint-config-prettier@^6.11.0: dependencies: get-stdin "^6.0.0" +eslint-plugin-jquery@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-jquery/-/eslint-plugin-jquery-1.5.1.tgz#d6bac643acf9484ce76394e27e2b07baca06662e" + integrity sha512-L7v1eaK5t80C0lvUXPFP9MKnBOqPSKhCOYyzy4LZ0+iK+TJwN8S9gAkzzP1AOhypRIwA88HF6phQ9C7jnOpW8w== + eslint-plugin-prettier@^3.1.4: version "3.1.4" resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.4.tgz#168ab43154e2ea57db992a2cd097c828171f75c2" From 77620c1699f0080b39f0b67405d070524438ca09 Mon Sep 17 00:00:00 2001 From: shibafu Date: Thu, 6 Aug 2020 09:53:50 +0900 Subject: [PATCH 3/6] =?UTF-8?q?x-csrf-token=E3=81=8C=E9=80=81=E3=82=89?= =?UTF-8?q?=E3=82=8C=E3=81=AD=E3=81=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/assets/js/app.ts | 73 +++++++++++++++--------------------- resources/assets/js/fetch.ts | 40 +++++++++++++------- 2 files changed, 58 insertions(+), 55 deletions(-) diff --git a/resources/assets/js/app.ts b/resources/assets/js/app.ts index 7267f57..1196678 100644 --- a/resources/assets/js/app.ts +++ b/resources/assets/js/app.ts @@ -1,5 +1,5 @@ import * as Cookies from 'js-cookie'; -import jqXHR = JQuery.jqXHR; +import { fetchPostJson, fetchDeleteJson, ResponseError } from './fetch'; require('./bootstrap'); @@ -41,57 +41,46 @@ $(() => { const isLiked = $this.data('liked'); if (isLiked) { - const callback = (data: any) => { - $this.data('liked', false); - $this.find('.oi-heart').removeClass('text-danger'); - - const count = data.ejaculation ? data.ejaculation.likes_count : 0; - $this.find('.like-count').text(count ? count : ''); - }; - - $.ajax({ - url: '/api/likes/' + encodeURIComponent(targetId), - method: 'delete', - type: 'json', - }) - .then(callback) - .catch(function (xhr: jqXHR) { - if (xhr.status === 404) { - callback(JSON.parse(xhr.responseText)); - return; + fetchDeleteJson(`/api/likes/${encodeURIComponent(targetId)}`) + .then((response) => { + if (response.status === 200 || response.status === 404) { + return response.json(); } + throw new ResponseError(response); + }) + .then((data) => { + $this.data('liked', false); + $this.find('.oi-heart').removeClass('text-danger'); - console.error(xhr); + const count = data.ejaculation ? data.ejaculation.likes_count : 0; + $this.find('.like-count').text(count ? count : ''); + }) + .catch((e) => { + console.error(e); alert('いいねを解除できませんでした。'); }); } else { - const callback = (data: any) => { - $this.data('liked', true); - $this.find('.oi-heart').addClass('text-danger'); + fetchPostJson('/api/likes', { id: targetId }) + .then((response) => { + if (response.status === 200 || response.status === 409) { + return response.json(); + } + throw new ResponseError(response); + }) + .then((data) => { + $this.data('liked', true); + $this.find('.oi-heart').addClass('text-danger'); - const count = data.ejaculation ? data.ejaculation.likes_count : 0; - $this.find('.like-count').text(count ? count : ''); - }; - - $.ajax({ - url: '/api/likes', - method: 'post', - type: 'json', - data: { - id: targetId, - }, - }) - .then(callback) - .catch(function (xhr: jqXHR) { - if (xhr.status === 409) { - callback(JSON.parse(xhr.responseText)); - return; - } else if (xhr.status === 401) { + const count = data.ejaculation ? data.ejaculation.likes_count : 0; + $this.find('.like-count').text(count ? count : ''); + }) + .catch((e) => { + if (e instanceof ResponseError && e.response.status === 401) { alert('いいねするためにはログインしてください。'); return; } - console.error(xhr); + console.error(e); alert('いいねできませんでした。'); }); } diff --git a/resources/assets/js/fetch.ts b/resources/assets/js/fetch.ts index d683e62..128aec4 100644 --- a/resources/assets/js/fetch.ts +++ b/resources/assets/js/fetch.ts @@ -17,41 +17,55 @@ const joinParamsToPath = (path: string, params: QueryParams) => const fetchWrapper = (path: string, options: RequestInit = {}) => fetch(path, { credentials: 'same-origin', - headers, + headers: { ...headers, ...options.headers }, ...options, }); -const fetchWithJson = (path: string, body: any, options: RequestInit = {}) => +const fetchWithJson = (path: string, body?: any, options: RequestInit = {}) => fetchWrapper(path, { - body: JSON.stringify(body), - headers: { 'Content-Type': 'application/json' }, + body: body && JSON.stringify(body), + headers: { 'Content-Type': 'application/json', ...options.headers }, ...options, }); -const fetchWithForm = (path: string, body: any, options: RequestInit = {}) => +const fetchWithForm = (path: string, body?: any, options: RequestInit = {}) => fetchWrapper(path, { - body: stringify(body), - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: body && stringify(body), + headers: { 'Content-Type': 'application/x-www-form-urlencoded', ...options.headers }, ...options, }); export const fetchGet = (path: string, params: QueryParams = {}, options: RequestInit = {}) => fetchWrapper(joinParamsToPath(path, params), { method: 'GET', ...options }); -export const fetchPostJson = (path: string, body: any, options: RequestInit = {}) => +export const fetchPostJson = (path: string, body?: any, options: RequestInit = {}) => fetchWithJson(path, body, { method: 'POST', ...options }); -export const fetchPostForm = (path: string, body: any, options: RequestInit = {}) => +export const fetchPostForm = (path: string, body?: any, options: RequestInit = {}) => fetchWithForm(path, body, { method: 'POST', ...options }); -export const fetchPutJson = (path: string, body: any, options: RequestInit = {}) => +export const fetchPutJson = (path: string, body?: any, options: RequestInit = {}) => fetchWithJson(path, body, { method: 'PUT', ...options }); -export const fetchPutForm = (path: string, body: any, options: RequestInit = {}) => +export const fetchPutForm = (path: string, body?: any, options: RequestInit = {}) => fetchWithForm(path, body, { method: 'PUT', ...options }); -export const fetchDeleteJson = (path: string, body: any, options: RequestInit = {}) => +export const fetchDeleteJson = (path: string, body?: any, options: RequestInit = {}) => fetchWithJson(path, body, { method: 'DELETE', ...options }); -export const fetchDeleteForm = (path: string, body: any, options: RequestInit = {}) => +export const fetchDeleteForm = (path: string, body?: any, options: RequestInit = {}) => fetchWithForm(path, body, { method: 'DELETE', ...options }); + +export class ResponseError extends Error { + response: Response; + + constructor(response: Response, ...rest: any) { + super(...rest); + if (Error.captureStackTrace) { + Error.captureStackTrace(this, ResponseError); + } + + this.name = 'ResponseError'; + this.response = response; + } +} From 3a1ec763ea4897217192fcff16a291a028c5a566 Mon Sep 17 00:00:00 2001 From: shibafu Date: Thu, 6 Aug 2020 21:15:36 +0900 Subject: [PATCH 4/6] =?UTF-8?q?fetch:=20=E5=85=B1=E9=80=9Aheader=E3=81=8C?= =?UTF-8?q?=E9=80=81=E3=82=89=E3=82=8C=E3=81=A6=E3=81=AA=E3=81=8B=E3=81=A3?= =?UTF-8?q?=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/assets/js/fetch.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/assets/js/fetch.ts b/resources/assets/js/fetch.ts index 128aec4..01e1431 100644 --- a/resources/assets/js/fetch.ts +++ b/resources/assets/js/fetch.ts @@ -17,22 +17,22 @@ const joinParamsToPath = (path: string, params: QueryParams) => const fetchWrapper = (path: string, options: RequestInit = {}) => fetch(path, { credentials: 'same-origin', - headers: { ...headers, ...options.headers }, ...options, + headers: { ...headers, ...options.headers }, }); const fetchWithJson = (path: string, body?: any, options: RequestInit = {}) => fetchWrapper(path, { + ...options, body: body && JSON.stringify(body), headers: { 'Content-Type': 'application/json', ...options.headers }, - ...options, }); const fetchWithForm = (path: string, body?: any, options: RequestInit = {}) => fetchWrapper(path, { + ...options, body: body && stringify(body), headers: { 'Content-Type': 'application/x-www-form-urlencoded', ...options.headers }, - ...options, }); export const fetchGet = (path: string, params: QueryParams = {}, options: RequestInit = {}) => From 7c32ab068d3c745147640e49d02e96224b6dfedf Mon Sep 17 00:00:00 2001 From: shibafu Date: Thu, 6 Aug 2020 21:31:53 +0900 Subject: [PATCH 5/6] =?UTF-8?q?jQuery.ajax=E3=81=AE=E6=9D=91=E3=82=92?= =?UTF-8?q?=E6=BB=85=E3=81=BC=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/assets/js/checkin.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/resources/assets/js/checkin.ts b/resources/assets/js/checkin.ts index 7902965..4a1bf50 100644 --- a/resources/assets/js/checkin.ts +++ b/resources/assets/js/checkin.ts @@ -1,6 +1,7 @@ import Vue from 'vue'; import TagInput from './components/TagInput.vue'; import MetadataPreview from './components/MetadataPreview.vue'; +import { fetchGet, ResponseError } from './fetch'; export const bus = new Vue({ name: 'EventBus' }); @@ -47,19 +48,18 @@ new Vue({ fetchMetadata(url: string) { this.metadataLoadState = MetadataLoadState.Loading; - $.ajax({ - url: '/api/checkin/card', - method: 'get', - type: 'json', - data: { - url, - }, - }) + fetchGet('/api/checkin/card', { url }) + .then((response) => { + if (!response.ok) { + throw new ResponseError(response); + } + return response.json(); + }) .then((data) => { this.metadata = data; this.metadataLoadState = MetadataLoadState.Success; }) - .catch((_e) => { + .catch(() => { this.metadata = null; this.metadataLoadState = MetadataLoadState.Failed; }); From 1482c33448c37182bc2fa0fd55ed3575678fac53 Mon Sep 17 00:00:00 2001 From: shibafu Date: Thu, 6 Aug 2020 21:31:59 +0900 Subject: [PATCH 6/6] =?UTF-8?q?jQuery.ajax=E5=90=91=E3=81=91=E3=81=AECSRF?= =?UTF-8?q?=20Token=E8=A8=AD=E5=AE=9A=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/assets/js/bootstrap.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/resources/assets/js/bootstrap.ts b/resources/assets/js/bootstrap.ts index 99d9e9b..fbfc914 100644 --- a/resources/assets/js/bootstrap.ts +++ b/resources/assets/js/bootstrap.ts @@ -1,17 +1,5 @@ // jQuery import './tissue'; -// Setup global request header -const token = document.head.querySelector('meta[name="csrf-token"]'); -if (!token) { - console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token'); -} else { - $.ajaxSetup({ - headers: { - 'X-CSRF-TOKEN': token.content, - }, - }); -} - // Bootstrap import 'bootstrap';