From d8e1caa6cfb9a4e01b69fcfe3bbcd340446268f5 Mon Sep 17 00:00:00 2001 From: xeltica Date: Fri, 24 Sep 2021 14:39:06 +0900 Subject: [PATCH] wip --- crowdin.yml | 4 + src/backend/controllers/session.ts | 9 +- src/common/functions/format.ts | 3 +- src/frontend/components/SettingPage.tsx | 136 ++++++++++++++++++++---- src/frontend/langs/en_US.json5 | 37 +++++-- src/frontend/langs/ja_JP.json5 | 33 ++++-- src/frontend/modal/dialog.ts | 1 + 7 files changed, 185 insertions(+), 38 deletions(-) create mode 100644 crowdin.yml diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 0000000..a17ed4b --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,4 @@ +files: + - source: /src/ja_JP.json5 + translation: /locales/%locale%.json5 + update_option: update_as_unapproved \ No newline at end of file diff --git a/src/backend/controllers/session.ts b/src/backend/controllers/session.ts index 28d1420..0f56d59 100644 --- a/src/backend/controllers/session.ts +++ b/src/backend/controllers/session.ts @@ -3,10 +3,10 @@ * @author Xeltica */ -import { Body, CurrentUser, Get, JsonController, OnUndefined, Post, Put } from 'routing-controllers'; +import { Body, CurrentUser, Delete, Get, JsonController, OnUndefined, Post, Put } from 'routing-controllers'; import { DeepPartial } from 'typeorm'; import { getScores } from '../functions/get-scores'; -import { updateUser } from '../functions/users'; +import { deleteUser, updateUser } from '../functions/users'; import { User } from '../models/entities/user'; import { sendAlert } from '../services/send-alert'; import { UserSetting } from './UserSetting'; @@ -38,6 +38,11 @@ export class SessionController { @Post('/alert') async testAlert(@CurrentUser({ required: true }) user: User) { await sendAlert(user); } + + @OnUndefined(204) + @Delete() async delete(@CurrentUser({ required: true }) user: User) { + await deleteUser(user.username, user.host); + } } diff --git a/src/common/functions/format.ts b/src/common/functions/format.ts index 75fd615..5ff2ff9 100644 --- a/src/common/functions/format.ts +++ b/src/common/functions/format.ts @@ -1,5 +1,4 @@ import { config } from '../../config'; -import { User } from '../../backend/models/entities/user'; import { Score } from '../types/score'; import { defaultTemplate } from '../../backend/const'; import { IUser } from '../types/user'; @@ -8,7 +7,7 @@ import { IUser } from '../types/user'; * 埋め込み変数の型 */ export type Variable = { - replace?: string | ((score: Score, user: User) => string); + replace?: string | ((score: Score, user: IUser) => string); }; /** diff --git a/src/frontend/components/SettingPage.tsx b/src/frontend/components/SettingPage.tsx index 15e3932..34cb780 100644 --- a/src/frontend/components/SettingPage.tsx +++ b/src/frontend/components/SettingPage.tsx @@ -1,4 +1,7 @@ import React, { useCallback, useEffect, useReducer } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useDispatch } from 'react-redux'; + import { alertModes } from '../../common/types/alert-mode'; import { IUser } from '../../common/types/user'; import { Visibility } from '../../common/types/visibility'; @@ -6,11 +9,22 @@ import { useGetSessionQuery } from '../services/session'; import { Card } from './Card'; import { Theme, themes } from '../misc/theme'; import { API_ENDPOINT, LOCALSTORAGE_KEY_TOKEN } from '../const'; -import { useDispatch } from 'react-redux'; import { changeLang, changeTheme, showModal } from '../store/slices/screen'; import { useSelector } from '../store'; import { languageName } from '../langs'; -import { useTranslation } from 'react-i18next'; + +const variables = [ + 'notesCount', + 'followingCount', + 'followersCount', + 'notesDelta', + 'followingDelta', + 'followersDelta', + 'url', + 'username', + 'host', + 'rating', +] as const; type SettingDraftType = Partial { } }, [data]); + const onClickInsertVariables = useCallback((e) => { + dispatch(showModal({ + type: 'menu', + screenX: e.screenX, + screenY: e.screenY, + items: Object.keys(variables).map(key => ({ + name: '_template.variables.' + key, + onClick: () => { console.log(key); }, + })), + })); + }, [dispatch, t, variables]); + + const onClickInsertVariablesHelp = useCallback(() => { + dispatch(showModal({ + type: 'dialog', + icon: 'info', + message: t('_template.insertVariablesHelp'), + })); + }, [dispatch, t]); + const onClickSendAlert = useCallback(() => { dispatch(showModal({ type: 'dialog', - title: 'アラートをテスト送信しますか?', - message: '現在の設定でアラートを送信します。設定が保存済みであるかどうか、実行前に必ずご確認ください。', + title: t('_sendTest.title'), + message: t('_sendTest.message'), icon: 'question', - buttons: 'yesNo', + buttons: [ + { + text: t('_sendTest.yes'), + style: 'primary', + }, + { + text: t('_sendTest.no'), + }, + ], onSelect(i) { if (i === 0) { fetch(`${API_ENDPOINT}session/alert`, { @@ -107,29 +149,37 @@ export const SettingPage: React.VFC = () => { }).then(() => { dispatch(showModal({ type: 'dialog', - message: '送信しました。', + message: t('_logout.success'), icon: 'info', })); }).catch((e) => { console.error(e); dispatch(showModal({ type: 'dialog', - message: '送信に失敗しました。', + message: t('_logout.failure'), icon: 'error', })); }); } }, })); - }, [dispatch]); + }, [dispatch, t]); const onClickLogout = useCallback(() => { dispatch(showModal({ type: 'dialog', - title: 'ログアウトしてもよろしいですか?', - message: 'ログアウトしても、アラート送信や、お使いのMisskeyアカウントのデータ収集といった機能は動作し続けます。Misskey Toolsの利用を停止したい場合は、「アカウント連携を解除する」ボタンを押下して下さい。', + title: t('_logout.title'), + message: t('_logout.message'), icon: 'question', - buttons: 'yesNo', + buttons: [ + { + text: t('_logout.yes'), + style: 'primary', + }, + { + text: t('_logout.no'), + }, + ], onSelect(i) { if (i === 0) { localStorage.removeItem(LOCALSTORAGE_KEY_TOKEN); @@ -137,14 +187,52 @@ export const SettingPage: React.VFC = () => { } }, })); - }, [dispatch]); + }, [dispatch, t]); const onClickDeleteAccount = useCallback(() => { dispatch(showModal({ type: 'dialog', - message: 'WIP', + title: t('_deactivate.title'), + message: t('_deactivate.message'), + icon: 'question', + buttons: [ + { + text: t('_deactivate.yes'), + style: 'danger', + }, + { + text: t('_deactivate.no'), + }, + ], + primaryClassName: 'danger', + onSelect(i) { + if (i === 0) { + fetch(`${API_ENDPOINT}session`, { + method: 'DELETE', + headers: { + 'Authorization': `Bearer ${localStorage[LOCALSTORAGE_KEY_TOKEN]}`, + }, + }).then(() => { + dispatch(showModal({ + type: 'dialog', + message: t('_deactivate.success'), + icon: 'info', + onSelect() { + location.reload(); + } + })); + }).catch((e) => { + console.error(e); + dispatch(showModal({ + type: 'dialog', + message: t('_deactivate.failure'), + icon: 'error', + })); + }); + } + }, })); - }, [dispatch]); + }, [dispatch, t]); const defaultTemplate = t('_template.default'); @@ -221,21 +309,29 @@ export const SettingPage: React.VFC = () => { )) } +
+ + {t('translatedByTheCommunity')}  + {t('helpTranslation')} +

{t('template')}

{t('_template.description')}

+
+ + +