From 3c65971f214b1c92ff5bfa80f5bfffce9d6def5c Mon Sep 17 00:00:00 2001 From: Ebise Lutica <7106976+Xeltica@users.noreply.github.com> Date: Sat, 7 May 2022 02:59:19 +0900 Subject: [PATCH 01/15] New translations ja-JP.json (Korean) --- src/frontend/langs/ko-KR.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/frontend/langs/ko-KR.json b/src/frontend/langs/ko-KR.json index 5b29d00..62d9e91 100644 --- a/src/frontend/langs/ko-KR.json +++ b/src/frontend/langs/ko-KR.json @@ -133,7 +133,8 @@ "url": "이 사이트의 URL", "username": "나의 유저네임", "host": "나의 인스턴스 호스트", - "rating": "레이팅" + "rating": "레이팅", + "gacha": "ガチャ" } }, "_error": { From a34de2c3688c1fd2676e3260ca702ff8a27c2c5c Mon Sep 17 00:00:00 2001 From: Ebise Lutica <7106976+Xeltica@users.noreply.github.com> Date: Sat, 7 May 2022 02:59:20 +0900 Subject: [PATCH 02/15] New translations ja-JP.json (Chinese Simplified) --- src/frontend/langs/zh-CN.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/frontend/langs/zh-CN.json b/src/frontend/langs/zh-CN.json index 0356754..3e215f0 100644 --- a/src/frontend/langs/zh-CN.json +++ b/src/frontend/langs/zh-CN.json @@ -133,7 +133,8 @@ "url": "本サイトのURL", "username": "アカウントのユーザー名", "host": "アカウントの所属ホスト名", - "rating": "レート" + "rating": "レート", + "gacha": "ガチャ" } }, "_error": { From 44e0879d68fb2fa58fa598e37e6dcc6696ac4cb8 Mon Sep 17 00:00:00 2001 From: Ebise Lutica <7106976+Xeltica@users.noreply.github.com> Date: Sat, 7 May 2022 02:59:21 +0900 Subject: [PATCH 03/15] New translations ja-JP.json (Chinese Traditional) --- src/frontend/langs/zh-TW.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/frontend/langs/zh-TW.json b/src/frontend/langs/zh-TW.json index 0356754..3e215f0 100644 --- a/src/frontend/langs/zh-TW.json +++ b/src/frontend/langs/zh-TW.json @@ -133,7 +133,8 @@ "url": "本サイトのURL", "username": "アカウントのユーザー名", "host": "アカウントの所属ホスト名", - "rating": "レート" + "rating": "レート", + "gacha": "ガチャ" } }, "_error": { From 8a34d18eac204c7b3d14e4a0fe6a212181416087 Mon Sep 17 00:00:00 2001 From: Ebise Lutica <7106976+Xeltica@users.noreply.github.com> Date: Sat, 7 May 2022 02:59:22 +0900 Subject: [PATCH 04/15] New translations ja-JP.json (English, United States) --- src/frontend/langs/en-US.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/frontend/langs/en-US.json b/src/frontend/langs/en-US.json index 7fce15d..e9ea207 100644 --- a/src/frontend/langs/en-US.json +++ b/src/frontend/langs/en-US.json @@ -133,7 +133,8 @@ "url": "URL of this site", "username": "Your Username", "host": "Your host", - "rating": "Your rating" + "rating": "Your rating", + "gacha": "ガチャ" } }, "_error": { From 3f1017fdeb97bab6299afa02dadc96e37623b8cb Mon Sep 17 00:00:00 2001 From: Ebise Lutica <7106976+Xeltica@users.noreply.github.com> Date: Mon, 9 May 2022 19:10:40 +0900 Subject: [PATCH 05/15] New translations ja-JP.json (Korean) --- src/frontend/langs/ko-KR.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/langs/ko-KR.json b/src/frontend/langs/ko-KR.json index 62d9e91..406ebbf 100644 --- a/src/frontend/langs/ko-KR.json +++ b/src/frontend/langs/ko-KR.json @@ -134,7 +134,7 @@ "username": "나의 유저네임", "host": "나의 인스턴스 호스트", "rating": "레이팅", - "gacha": "ガチャ" + "gacha": "가챠" } }, "_error": { From ec4e28bfee33dd02a4cc92d4d98878a77063496b Mon Sep 17 00:00:00 2001 From: Ebise Lutica <7106976+Xeltica@users.noreply.github.com> Date: Mon, 9 May 2022 19:16:07 +0900 Subject: [PATCH 06/15] New translations ja-JP.json (Korean) --- src/frontend/langs/ko-KR.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/langs/ko-KR.json b/src/frontend/langs/ko-KR.json index 406ebbf..db5a391 100644 --- a/src/frontend/langs/ko-KR.json +++ b/src/frontend/langs/ko-KR.json @@ -12,7 +12,7 @@ "template": "템플릿", "sendAlert": "테스트 알림 보내기", "sendAlertDescription": "현재 설정으로 테스트 알림을 보냅니다.", - "sendAlertDisabled": "알림 수단이 '보내지 않음'으로 설정되어 있어 테스트하지 못했습니다.", + "sendAlertDisabled": "알림 수단이 '보내지 않음'으로 설정되어 있어 테스트할 수 없습니다.", "logout": "로그아웃", "logoutDescription": "로그아웃하더라도 알림은 계속해서 보내집니다.", "deleteAccount": "계정 연동을 해제하기", From 7c2a614ca2efbda11ea57c74a8bc1d230fc48d44 Mon Sep 17 00:00:00 2001 From: Ebise Lutica <7106976+Xeltica@users.noreply.github.com> Date: Wed, 11 May 2022 17:59:54 +0900 Subject: [PATCH 07/15] New translations ja-JP.json (Korean) --- src/frontend/langs/ko-KR.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/langs/ko-KR.json b/src/frontend/langs/ko-KR.json index db5a391..06f02b4 100644 --- a/src/frontend/langs/ko-KR.json +++ b/src/frontend/langs/ko-KR.json @@ -134,7 +134,7 @@ "username": "나의 유저네임", "host": "나의 인스턴스 호스트", "rating": "레이팅", - "gacha": "가챠" + "gacha": "랜덤 아무말" } }, "_error": { From 2301fe5eff764d4560013ae4e1bde573adfe5144 Mon Sep 17 00:00:00 2001 From: Xeltica Date: Thu, 9 Jun 2022 12:20:13 +0900 Subject: [PATCH 08/15] wip --- package.json | 2 + src/backend/views/frontend.pug | 2 +- src/frontend/App.tsx | 107 ++++++--------- src/frontend/GeneralLayout.tsx | 118 ++++++++++++++++ src/frontend/Header.tsx | 31 +++++ src/frontend/Modal.tsx | 8 +- src/frontend/Router.tsx | 25 ++++ src/frontend/components/AnnouncementList.tsx | 2 +- src/frontend/components/CurrentUser.tsx | 2 +- src/frontend/components/DeveloperInfo.tsx | 8 +- src/frontend/components/Header.tsx | 54 -------- src/frontend/components/RankingPage.tsx | 14 -- src/frontend/const.ts | 2 + src/frontend/hooks/useTitle.ts | 13 ++ src/frontend/init.tsx | 23 +++- src/frontend/langs/index.ts | 12 +- src/frontend/langs/ja-JP.json | 11 ++ src/frontend/modal/menu.ts | 2 +- .../AccountsPage.tsx => pages/account.tsx} | 11 +- .../AdminPage.tsx => pages/admin.tsx} | 118 ++++++++-------- src/frontend/pages/announcement.tsx | 5 +- .../apps/avatar-cropper.tsx} | 10 +- .../apps/misshai.scss} | 6 +- .../apps/misshai.tsx} | 126 ++++++++---------- .../pages/{ => apps/misshai}/ranking.tsx | 4 +- src/frontend/pages/index.session.tsx | 108 +++++++-------- src/frontend/pages/index.welcome.tsx | 12 +- .../SettingPage.tsx => pages/settings.tsx} | 13 +- src/frontend/pages/term.tsx | 35 ----- src/frontend/store/slices/screen.ts | 34 +++-- src/frontend/style.scss | 13 ++ yarn.lock | 16 ++- 32 files changed, 547 insertions(+), 400 deletions(-) create mode 100644 src/frontend/GeneralLayout.tsx create mode 100644 src/frontend/Header.tsx create mode 100644 src/frontend/Router.tsx delete mode 100644 src/frontend/components/Header.tsx delete mode 100644 src/frontend/components/RankingPage.tsx create mode 100644 src/frontend/hooks/useTitle.ts rename src/frontend/{components/AccountsPage.tsx => pages/account.tsx} (87%) rename src/frontend/{components/AdminPage.tsx => pages/admin.tsx} (64%) rename src/frontend/{components/NekomimiPage.tsx => pages/apps/avatar-cropper.tsx} (95%) rename src/frontend/{components/MisshaiPage.scss => pages/apps/misshai.scss} (83%) rename src/frontend/{components/MisshaiPage.tsx => pages/apps/misshai.tsx} (78%) rename src/frontend/pages/{ => apps/misshai}/ranking.tsx (73%) rename src/frontend/{components/SettingPage.tsx => pages/settings.tsx} (91%) delete mode 100644 src/frontend/pages/term.tsx diff --git a/package.json b/package.json index 2c56673..e83403d 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "dependencies": { "@babel/preset-react": "^7.14.5", "@reduxjs/toolkit": "^1.6.1", + "@types/deepmerge": "^2.2.0", "@types/insert-text-at-cursor": "^0.3.0", "@types/koa-bodyparser": "^4.3.0", "@types/koa-multer": "^1.0.0", @@ -45,6 +46,7 @@ "class-validator": "^0.13.1", "css-loader": "^6.2.0", "dayjs": "^1.10.7", + "deepmerge": "^4.2.2", "delay": "^4.4.0", "fibers": "^5.0.0", "i18next": "^20.6.1", diff --git a/src/backend/views/frontend.pug b/src/backend/views/frontend.pug index fd638fd..94f7f0c 100644 --- a/src/backend/views/frontend.pug +++ b/src/backend/views/frontend.pug @@ -15,7 +15,7 @@ html meta(name='twitter:site' content='@Xeltica') meta(name='twitter:creator' content='@Xeltica') link(rel="stylesheet" href="https://koruri.chillout.chat/koruri.css") - link(rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.2/font/bootstrap-icons.css") + script(src='https://kit.fontawesome.com/c7ab6eba70.js' crossorigin='anonymous') style. .loading { display: flex; diff --git a/src/frontend/App.tsx b/src/frontend/App.tsx index d906625..3b08b17 100644 --- a/src/frontend/App.tsx +++ b/src/frontend/App.tsx @@ -1,91 +1,64 @@ -import React from 'react'; -import { BrowserRouter, Link, Route, Switch, useLocation } from 'react-router-dom'; -import { Provider } from 'react-redux'; +import React, { useEffect } from 'react'; +import { BrowserRouter, useLocation } from 'react-router-dom'; +import { Provider, useDispatch } from 'react-redux'; import { useTranslation } from 'react-i18next'; -import i18n from 'i18next'; -import { initReactI18next } from 'react-i18next'; -import { IndexPage } from './pages'; -import { RankingPage } from './pages/ranking'; -import { Header } from './components/Header'; -import { TermPage } from './pages/term'; import { store } from './store'; import { ModalComponent } from './Modal'; import { useTheme } from './misc/theme'; -import { getBrowserLanguage, resources } from './langs'; -import { LOCALSTORAGE_KEY_LANG, XELTICA_STUDIO_URL } from './const'; +import { BREAKPOINT_SM, XELTICA_STUDIO_URL } from './const'; import { useGetSessionQuery } from './services/session'; -import { AnnouncementPage } from './pages/announcement'; - -import 'xeltica-ui/dist/css/xeltica-ui.min.css'; -import './style.scss'; - -document.body.classList.add('dark'); - -if (!localStorage[LOCALSTORAGE_KEY_LANG]) { - localStorage[LOCALSTORAGE_KEY_LANG] = getBrowserLanguage(); -} - -i18n - .use(initReactI18next) - .init({ - resources, - lng: localStorage[LOCALSTORAGE_KEY_LANG], - interpolation: { - escapeValue: false // react already safes from xss - } - }); +import { Router } from './Router'; +import { setMobile } from './store/slices/screen'; +import { GeneralLayout } from './GeneralLayout'; const AppInner : React.VFC = () => { const { data: session } = useGetSessionQuery(undefined); const $location = useLocation(); + const dispatch = useDispatch(); + useTheme(); const {t} = useTranslation(); const error = (window as any).__misshaialert?.error; - return error ? ( -
-
-
-

{t('error')}

-

{t('_error.sorry')}

-

- {t('_error.additionalInfo')} - {t(`_error.${error}`)} -

- (window as any).__misshaialert.error = null}>{t('retry')} -
-
- ) : ( -
- {$location.pathname !== '/' &&
} - - - - - - + useEffect(() => { + const qMobile = window.matchMedia(`(max-width: ${BREAKPOINT_SM})`); + const syncMobile = (ev: MediaQueryListEvent) => dispatch(setMobile(ev.matches)); + dispatch(setMobile(qMobile.matches)); + qMobile.addEventListener('change', syncMobile); + + return () => { + qMobile.removeEventListener('change', syncMobile); + }; + }, []); + + const TheLayout = session || $location.pathname !== '/' ? GeneralLayout : 'div'; + + return ( + + {error ? ( +
+

{t('error')}

+

{t('_error.sorry')}

+

+ {t('_error.additionalInfo')} + {t(`_error.${error}`)} +

+
+ ) : } -
+ ); }; diff --git a/src/frontend/GeneralLayout.tsx b/src/frontend/GeneralLayout.tsx new file mode 100644 index 0000000..b4a1b7c --- /dev/null +++ b/src/frontend/GeneralLayout.tsx @@ -0,0 +1,118 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { NavLink } from 'react-router-dom'; +import styled from 'styled-components'; + +import { useGetMetaQuery, useGetSessionQuery } from './services/session'; +import { useSelector } from './store'; + +type IsMobileProp = { isMobile: boolean }; + +const Container = styled.div` + padding: var(--margin); + position: relative; +`; + +const Sidebar = styled.nav` + width: 320px; + position: fixed; + top: var(--margin); + left: var(--margin); +`; + +const Main = styled.main` + flex: 1; + margin-top: 80px; + margin-left: ${p => !p.isMobile ? `${320 + 16}px` : 0}; + min-width: 0; +`; + +const MobileHeader = styled.header` + position: fixed; + top: 0; + left: 0; + right: 0; + height: 64px; + background: var(--panel); + > h1 { + font-size: 1rem; + margin-bottom: 0; + } +`; + +export const GeneralLayout: React.FC = ({children}) => { + const { data: session } = useGetSessionQuery(undefined); + const { data: meta } = useGetMetaQuery(undefined); + const { isMobile, title } = useSelector(state => state.screen); + const {t} = useTranslation(); + + const navLinkClassName = (isActive: boolean) => `item ${isActive ? 'active' : ''}`; + + return ( + + {isMobile && ( + + +

{t(title ?? 'title')}

+
+ )} +
+ {!isMobile && ( + +

{t('title')}

+
+
+ + + {t('_sidebar.dashboard')} + +
+
+

{t('_sidebar.tools')}

+ + + {t('_sidebar.missHaiAlert')} + + + + {t('_sidebar.cropper')} + +
+
+ {session &&

{session.username}@{session.host}

} + {session && ( + + + {t('_sidebar.accounts')} + + )} + + + {t('_sidebar.settings')} + + + + {t('_sidebar.admin')} + +
+
+
+ )} +
+ {session && meta && meta.currentTokenVersion !== session.tokenVersion && ( +
+ + {t('shouldUpdateToken')} + + {t('update')} + +
+ )} + {children} +
+
+
+ ); +}; diff --git a/src/frontend/Header.tsx b/src/frontend/Header.tsx new file mode 100644 index 0000000..a122828 --- /dev/null +++ b/src/frontend/Header.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; + +import { useTranslation } from 'react-i18next'; +import { useGetSessionQuery } from './services/session'; +import { useSelector } from './store'; + +export type HeaderProps = { + title?: string; +}; + +export const Header: React.FC = ({title}) => { + const { t } = useTranslation(); + const { data } = useGetSessionQuery(undefined); + const { isMobile } = useSelector(state => state.screen); + + return ( +
+

+ {{t('title')}} + {title && <> / {title}} +

+ {data && ( + + )} +
+ ); +}; diff --git a/src/frontend/Modal.tsx b/src/frontend/Modal.tsx index 5cf3275..c6db066 100644 --- a/src/frontend/Modal.tsx +++ b/src/frontend/Modal.tsx @@ -23,10 +23,10 @@ const getButtons = (button: DialogButtonType): DialogButton[] => { }; const dialogIconPattern: Record = { - error: 'bi bi-x-circle-fill text-danger', - info: 'bi bi-info-circle-fill text-primary', - question: 'bi bi-question-circle-fill text-primary', - warning: 'bi bi-exclamation-circle-fill text-warning', + error: 'fas fa-circle-xmark text-danger', + info: 'fas fa-circle-info text-primary', + question: 'fas fa-circle-question text-primary', + warning: 'fas fa-circle-exclamation text-warning', }; const Dialog: React.VFC<{modal: ModalTypeDialog}> = ({modal}) => { diff --git a/src/frontend/Router.tsx b/src/frontend/Router.tsx new file mode 100644 index 0000000..ed7692d --- /dev/null +++ b/src/frontend/Router.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { Route, Switch } from 'react-router-dom'; +import { IndexPage } from './pages'; +import { AnnouncementPage } from './pages/announcement'; +import { RankingPage } from './pages/apps/misshai/ranking'; +import { AdminPage } from './pages/admin'; +import { AccountsPage } from './pages/account'; +import { SettingPage } from './pages/settings'; +import { MisshaiPage } from './pages/apps/misshai'; +import { NekomimiPage } from './pages/apps/avatar-cropper'; + +export const Router: React.VFC = () => { + return ( + + + + + + + + + + + ); +}; diff --git a/src/frontend/components/AnnouncementList.tsx b/src/frontend/components/AnnouncementList.tsx index 93f3279..04f8b20 100644 --- a/src/frontend/components/AnnouncementList.tsx +++ b/src/frontend/components/AnnouncementList.tsx @@ -24,7 +24,7 @@ export const AnnouncementList: React.VFC = () => { return ( <> -

{t('announcements')}

+

{t('announcements')}

{announcements.map(a => ( diff --git a/src/frontend/components/CurrentUser.tsx b/src/frontend/components/CurrentUser.tsx index 0c5b00c..4d88e93 100644 --- a/src/frontend/components/CurrentUser.tsx +++ b/src/frontend/components/CurrentUser.tsx @@ -5,6 +5,6 @@ import { Skeleton } from './Skeleton'; export const CurrentUser: React.VFC = () => { const {data} = useGetSessionQuery(undefined); return data ? ( -

{data.username}@{data.host}

+

{data.username}@{data.host}

) : ; }; diff --git a/src/frontend/components/DeveloperInfo.tsx b/src/frontend/components/DeveloperInfo.tsx index 26fe6f9..f371f99 100644 --- a/src/frontend/components/DeveloperInfo.tsx +++ b/src/frontend/components/DeveloperInfo.tsx @@ -5,19 +5,19 @@ export const DeveloperInfo: React.VFC = () => { const {t} = useTranslation(); return ( <> -

{t('_developerInfo.title')}

+

{t('_developerInfo.title')}

{t('_developerInfo.description')}

diff --git a/src/frontend/components/Header.tsx b/src/frontend/components/Header.tsx deleted file mode 100644 index 05869d6..0000000 --- a/src/frontend/components/Header.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import React, { HTMLProps, useMemo, useState } from 'react'; -import { Link } from 'react-router-dom'; - -import { useTranslation } from 'react-i18next'; -import { useGetMetaQuery, useGetSessionQuery } from '../services/session'; -import { CHANGELOG_URL } from '../const'; -import { createGacha } from '../../common/functions/create-gacha'; - -export type HeaderProps = { - hasTopLink?: boolean; - className?: HTMLProps['className'], - style?: HTMLProps['style'], -}; - -export const Header: React.FC = ({hasTopLink, children, className, style}) => { - const {data: meta} = useGetMetaQuery(undefined); - const {data: session} = useGetSessionQuery(undefined); - const { t } = useTranslation(); - const [generation, setGeneration] = useState(0); - const gacha = useMemo(() => createGacha(), [generation]); - - - - return ( -
-
-

- {hasTopLink ? {t('title')} : t('title')} - {meta && ( - - v{meta?.version} - - )} -

-

- - {gacha} - {session && ( - - - - )} -

- {children} -
-
- ); -}; diff --git a/src/frontend/components/RankingPage.tsx b/src/frontend/components/RankingPage.tsx deleted file mode 100644 index e9a9e63..0000000 --- a/src/frontend/components/RankingPage.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React, { useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { Ranking } from './Ranking'; - -export const RankingPage: React.VFC = () => { - const [limit, setLimit] = useState(10); - const {t} = useTranslation(); - return ( -
- - {limit && } -
- ); -}; diff --git a/src/frontend/const.ts b/src/frontend/const.ts index 1983a42..e0ad2cb 100644 --- a/src/frontend/const.ts +++ b/src/frontend/const.ts @@ -7,3 +7,5 @@ export const API_ENDPOINT = `//${location.host}/api/v1/`; export const CHANGELOG_URL = 'https://github.com/Xeltica/MisskeyTools/blob/master/CHANGELOG.md'; export const XELTICA_STUDIO_URL = 'https://xeltica.work'; + +export const BREAKPOINT_SM = '800px'; diff --git a/src/frontend/hooks/useTitle.ts b/src/frontend/hooks/useTitle.ts new file mode 100644 index 0000000..8a997bf --- /dev/null +++ b/src/frontend/hooks/useTitle.ts @@ -0,0 +1,13 @@ +import { useEffect } from 'react'; +import { useDispatch } from 'react-redux'; +import { setTitle } from '../store/slices/screen'; + +export const useTitle = (title: string) => { + const dispatch = useDispatch(); + useEffect(() => { + dispatch(setTitle(title)); + return () => { + dispatch(setTitle(null)); + }; + }, [title]); +}; diff --git a/src/frontend/init.tsx b/src/frontend/init.tsx index fb5e79f..e1e2a77 100644 --- a/src/frontend/init.tsx +++ b/src/frontend/init.tsx @@ -2,10 +2,31 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import relativeTime from 'dayjs/plugin/relativeTime'; import dayjs from 'dayjs'; -import 'dayjs/locale/ja'; +import i18n from 'i18next'; +import { initReactI18next } from 'react-i18next'; +import { getBrowserLanguage, resources } from './langs'; import { App } from './App'; +import { LOCALSTORAGE_KEY_LANG } from './const'; + +import 'xeltica-ui/dist/css/xeltica-ui.min.css'; +import './style.scss'; +import 'dayjs/locale/ja'; dayjs.extend(relativeTime); +if (!localStorage[LOCALSTORAGE_KEY_LANG]) { + localStorage[LOCALSTORAGE_KEY_LANG] = getBrowserLanguage(); +} + +i18n + .use(initReactI18next) + .init({ + resources, + lng: localStorage[LOCALSTORAGE_KEY_LANG], + interpolation: { + escapeValue: false // Reactは常にXSS対策をしてくれるので、i18next側では対応不要 + } + }); + ReactDOM.render(, document.getElementById('app')); diff --git a/src/frontend/langs/index.ts b/src/frontend/langs/index.ts index ec8fa0d..4eeda0b 100644 --- a/src/frontend/langs/index.ts +++ b/src/frontend/langs/index.ts @@ -2,10 +2,18 @@ import jaJP from './ja-JP.json'; import enUS from './en-US.json'; import koKR from './ko-KR.json'; +import deepmerge from 'deepmerge'; + +const merge = (baseData: Record, newData: Record) => { + return deepmerge(baseData, newData, { + isMergeableObject: obj => typeof obj === 'object' + }); +}; + export const resources = { 'ja_JP': { translation: jaJP }, - 'en_US': { translation: enUS }, - 'ko_KR': { translation: koKR }, + 'en_US': { translation: merge(jaJP, enUS) }, + 'ko_KR': { translation: merge(jaJP, koKR) }, }; export const languageName = { diff --git a/src/frontend/langs/ja-JP.json b/src/frontend/langs/ja-JP.json index 92d45af..4fad25e 100644 --- a/src/frontend/langs/ja-JP.json +++ b/src/frontend/langs/ja-JP.json @@ -49,6 +49,17 @@ "update": "更新する", "shareMisskeyTools": "#MisskeyTools をシェアする", "shareMisskeyToolsNote": "#MisskeyTools はいいぞ\n\nhttps://misskey.tools", + "_sidebar": { + "dashboard": "ダッシュボード", + "tools": "ツール", + "manageTools": "ツールを管理", + "missHaiAlert": "ミス廃アラート", + "cropper": "ねこみみアジャスター", + "accounts": "アカウント", + "settings": "設定", + "admin": "管理画面", + "about": "Misskey Toolsについて" + }, "_welcomeMessage": { "pattern1": "ついついノートしすぎていませんか?", "pattern2": "Misskey, しすぎていませんか?", diff --git a/src/frontend/modal/menu.ts b/src/frontend/modal/menu.ts index bce02a6..e65e87a 100644 --- a/src/frontend/modal/menu.ts +++ b/src/frontend/modal/menu.ts @@ -5,7 +5,7 @@ export interface ModalTypeMenu { items: MenuItem[]; } -export type MenuItemClassName = `bi bi-${string}`; +export type MenuItemClassName = `fas fa-${string}`; export interface MenuItem { icon?: MenuItemClassName; diff --git a/src/frontend/components/AccountsPage.tsx b/src/frontend/pages/account.tsx similarity index 87% rename from src/frontend/components/AccountsPage.tsx rename to src/frontend/pages/account.tsx index 8b6e371..8660f31 100644 --- a/src/frontend/components/AccountsPage.tsx +++ b/src/frontend/pages/account.tsx @@ -5,14 +5,17 @@ import { LOCALSTORAGE_KEY_ACCOUNTS, LOCALSTORAGE_KEY_TOKEN } from '../const'; import { useGetSessionQuery } from '../services/session'; import { useSelector } from '../store'; import { setAccounts } from '../store/slices/screen'; -import { LoginForm } from './LoginForm'; -import { Skeleton } from './Skeleton'; +import { LoginForm } from '../components/LoginForm'; +import { Skeleton } from '../components/Skeleton'; +import { useTitle } from '../hooks/useTitle'; export const AccountsPage: React.VFC = () => { const {data} = useGetSessionQuery(undefined); const {t} = useTranslation(); const dispatch = useDispatch(); + useTitle('_sidebar.accounts'); + const {accounts, accountTokens} = useSelector(state => state.screen); const switchAccount = (token: string) => { @@ -40,14 +43,14 @@ export const AccountsPage: React.VFC = () => { accounts.length === accountTokens.length ? ( accounts.map(account => ( )) diff --git a/src/frontend/components/AdminPage.tsx b/src/frontend/pages/admin.tsx similarity index 64% rename from src/frontend/components/AdminPage.tsx rename to src/frontend/pages/admin.tsx index a2cd132..8a3efa2 100644 --- a/src/frontend/components/AdminPage.tsx +++ b/src/frontend/pages/admin.tsx @@ -2,12 +2,12 @@ import React, { useEffect, useState } from 'react'; import { LOCALSTORAGE_KEY_TOKEN } from '../const'; import { useGetSessionQuery } from '../services/session'; -import { Skeleton } from './Skeleton'; +import { Skeleton } from '../components/Skeleton'; import { IAnnouncement } from '../../common/types/announcement'; import { $delete, $get, $post, $put } from '../misc/api'; -import { Card } from './Card'; import { showModal } from '../store/slices/screen'; import { useDispatch } from 'react-redux'; +import { useTitle } from '../hooks/useTitle'; export const AdminPage: React.VFC = () => { @@ -15,6 +15,8 @@ export const AdminPage: React.VFC = () => { const dispatch = useDispatch(); + useTitle('_sidebar.admin'); + const [announcements, setAnnouncements] = useState([]); const [selectedAnnouncement, selectAnnouncement] = useState(null); const [isAnnouncementsLoaded, setAnnouncementsLoaded] = useState(false); @@ -132,64 +134,66 @@ export const AdminPage: React.VFC = () => {

You are not an administrator and cannot open this page.

) : ( <> -

Announcements

- {!isEditMode && ( - - )} - - { !isEditMode ? ( - <> - {isDeleteMode &&
Click the item to delete.
} -
- {announcements.map(a => ( - + ))} + {!isDeleteMode && ( + + )} +
+ + ) : ( +
+ +