0
0
Fork 0

style: use space indentation

feat: no new users allowed
fix: change base language to Korean
fix: change gacha to random text from devs
This commit is contained in:
아르페 2024-02-02 01:35:17 +09:00
parent 320dfc0696
commit b6a3b0cd53
35 changed files with 335 additions and 367 deletions

View file

@ -6,4 +6,4 @@ end_of_line = lf
charset = utf-8 charset = utf-8
trim_trailing_whitespace = true trim_trailing_whitespace = true
insert_final_newline = true insert_final_newline = true
indent_size = 4 indent_size = 2

View file

@ -5,16 +5,16 @@ const entities = require('./built/backend/services/db').entities;
const config = Object.freeze(JSON.parse(fs.readFileSync(__dirname + '/config.json', 'utf-8'))); const config = Object.freeze(JSON.parse(fs.readFileSync(__dirname + '/config.json', 'utf-8')));
module.exports = { module.exports = {
type: 'postgres', type: 'postgres',
host: config.db.host, host: config.db.host,
port: config.db.port, port: config.db.port,
username: config.db.user, username: config.db.user,
password: config.db.pass, password: config.db.pass,
database: config.db.db, database: config.db.db,
extra: config.db.extra, extra: config.db.extra,
entities: entities, entities: entities,
migrations: ['migration/*.ts'], migrations: ['migration/*.ts'],
cli: { cli: {
migrationsDir: 'migration' migrationsDir: 'migration'
} }
}; };

View file

@ -16,35 +16,9 @@ export const misskeyAppInfo = {
permission: [ permission: [
'read:account', 'read:account',
'write:account', 'write:account',
'read:blocks',
'write:blocks',
'read:drive',
'write:drive',
'read:favorites',
'write:favorites',
'read:following',
'write:following',
'read:messaging',
'write:messaging',
'read:mutes',
'write:mutes',
'write:notes', 'write:notes',
'read:notifications', 'read:notifications',
'write:notifications', 'write:notifications',
'read:reactions',
'write:reactions',
'write:votes',
'read:pages',
'write:pages',
'write:page-likes',
'read:page-likes',
'read:user-groups',
'write:user-groups',
'read:channels', 'read:channels',
'write:channels',
'read:gallery',
'write:gallery',
'read:gallery-likes',
'write:gallery-likes',
], ],
} as const; } as const;

View file

@ -24,5 +24,5 @@ export class UserSetting {
useRanking?: boolean; useRanking?: boolean;
@IsOptional() @IsOptional()
appendHashtag?: boolean; appendHashtag?: boolean;
} }

View file

@ -180,8 +180,12 @@ router.get('(.*)', async (ctx) => {
async function login(ctx: Context, user: Record<string, unknown>, host: string, token: string) { async function login(ctx: Context, user: Record<string, unknown>, host: string, token: string) {
const isNewcomer = !(await getUser(user.username as string, host)); const isNewcomer = !(await getUser(user.username as string, host));
await upsertUser(user.username as string, host, token); if (isNewcomer) {
await die(ctx, 'noNewUserAllowed', 403);
return;
}
await upsertUser(user.username as string, host, token);
const u = await getUser(user.username as string, host); const u = await getUser(user.username as string, host);
if (!u) { if (!u) {

View file

@ -38,23 +38,23 @@ export const work = async () => {
await calculateAllRating(groupedUsers); await calculateAllRating(groupedUsers);
} }
catch (e) { catch (e) {
printLog('Misskey Tools with LycheeBridge 레이팅 계산에 실패했습니다.', 'error'); printLog('Misskey Tools with LycheeBridge 레이팅 계산에 실패했습니다.', 'error');
printLog(e instanceof Error ? errorToString(e) : JSON.stringify(e, null, ' '), 'error'); printLog(e instanceof Error ? errorToString(e) : JSON.stringify(e, null, ' '), 'error');
Store.dispatch({ nowCalculating: false }); Store.dispatch({ nowCalculating: false });
} }
finally { finally {
Store.dispatch({ nowCalculating: false }); Store.dispatch({ nowCalculating: false });
printLog(`${users.length}개의 계정 레이팅 계산이 완료되었습니다.`); printLog(`${users.length}개의 계정 레이팅 계산이 완료되었습니다.`);
} }
try { try {
printLog(`${users.length}개의 계정에 알림을 전송하고 있습니다.`); printLog(`${users.length}개의 계정에 알림을 전송하고 있습니다.`);
await sendAllAlerts(groupedUsers); await sendAllAlerts(groupedUsers);
} catch (e) { } catch (e) {
printLog('Misskey Tools with LycheeBridge 알림 전송에 실패했습니다.', 'error'); printLog('Misskey Tools with LycheeBridge 알림 전송에 실패했습니다.', 'error');
printLog(e instanceof Error ? errorToString(e) : JSON.stringify(e, null, ' '), 'error'); printLog(e instanceof Error ? errorToString(e) : JSON.stringify(e, null, ' '), 'error');
} finally { } finally {
printLog('Misskey Tools with LycheeBridge 알림 전송이 완료되었습니다.'); printLog('Misskey Tools with LycheeBridge 알림 전송이 완료되었습니다.');
} }
}; };
@ -75,10 +75,10 @@ const calculateRating = async (host: string, users: User[]) => {
// ユーザーが削除されている場合、レコードからも消してとりやめ // ユーザーが削除されている場合、レコードからも消してとりやめ
printLog(`${toAcct(user)} 게정이 삭제, 정지, 또는 토큰이 제거된 것으로 보이며, 시스템에서 계정이 제거되었습니다.`, 'warn'); printLog(`${toAcct(user)} 게정이 삭제, 정지, 또는 토큰이 제거된 것으로 보이며, 시스템에서 계정이 제거되었습니다.`, 'warn');
await deleteUser(user.username, user.host); await deleteUser(user.username, user.host);
continue; continue;
} else { } else {
printLog(`Misskey 오류: ${JSON.stringify(e.error)}`, 'error'); printLog(`Misskey 오류: ${JSON.stringify(e.error)}`, 'error');
continue; continue;
} }
} else if (e instanceof TimedOutError) { } else if (e instanceof TimedOutError) {
printLog(`${user.host} 인스턴스로의 연결에 실패하여 레이팅 계산을 중단합니다.`, 'error'); printLog(`${user.host} 인스턴스로의 연결에 실패하여 레이팅 계산을 중단합니다.`, 'error');
@ -105,7 +105,7 @@ const sendAlerts = async (host: string, users: User[]) => {
.map(user => { .map(user => {
const count = userScoreCache.get(toAcct(user)); const count = userScoreCache.get(toAcct(user));
if (count == null) return null; if (count == null) return null;
if (count.notesCount - (user.prevNotesCount ?? 0) <= 1) return null; if (count.notesCount - (user.prevNotesCount ?? 0) <= 1) return null;
return { return {
user, user,
count, count,
@ -121,31 +121,31 @@ const sendAlerts = async (host: string, users: User[]) => {
// 通知 // 通知
for (const {user, count, message} of models.filter(m => m.user.alertMode === 'notification' || m.user.alertMode === 'both')) { for (const {user, count, message} of models.filter(m => m.user.alertMode === 'notification' || m.user.alertMode === 'both')) {
try { try {
await sendNotificationAlert(message, user); await sendNotificationAlert(message, user);
} catch (e) { } catch (e) {
printLog('Misskey Tools with LycheeBridge 알림 전송에 실패했습니다.', 'error'); printLog('Misskey Tools with LycheeBridge 알림 전송에 실패했습니다.', 'error');
printLog(e instanceof Error ? errorToString(e) : JSON.stringify(e, null, ' '), 'error'); printLog(e instanceof Error ? errorToString(e) : JSON.stringify(e, null, ' '), 'error');
} finally { } finally {
if (user.alertMode === 'notification') { if (user.alertMode === 'notification') {
await updateScore(user, count); await updateScore(user, count);
} }
} }
} }
// アラート // アラート
for (const {user, count, message} of models.filter(m => m.user.alertMode === 'note' || m.user.alertMode === 'both')) { for (const {user, count, message} of models.filter(m => m.user.alertMode === 'note' || m.user.alertMode === 'both')) {
try { try {
await sendNoteAlert(message, user); await sendNoteAlert(message, user);
} catch (e) { } catch (e) {
printLog('Misskey Tools with LycheeBridge 알림 전송에 실패했습니다.', 'error'); printLog('Misskey Tools with LycheeBridge 알림 전송에 실패했습니다.', 'error');
printLog(e instanceof Error ? errorToString(e) : JSON.stringify(e, null, ' '), 'error'); printLog(e instanceof Error ? errorToString(e) : JSON.stringify(e, null, ' '), 'error');
} finally { } finally {
await Promise.all([ await Promise.all([
updateScore(user, count), updateScore(user, count),
delay(1000), delay(1000),
]); ]);
} }
} }
printLog(`${host} 인스턴스의 사용자 ${users.length}명의 알림 전송이 완료되었습니다.`); printLog(`${host} 인스턴스의 사용자 ${users.length}명의 알림 전송이 완료되었습니다.`);

View file

@ -1,41 +1,41 @@
doctype html doctype html
html html
head head
meta(charset="UTF-8") meta(charset="UTF-8")
meta(name="viewport", content="width=device-width, initial-scale=1.0") meta(name="viewport", content="width=device-width, initial-scale=1.0")
block meta block meta
- const title = t ? `${t} | Misskey Tools`: 'Misskey Tools with LycheeBridge'; - const title = t ? `${t} | Misskey Tools`: 'Misskey Tools with LycheeBridge';
- const desc = d || '🌠 연합우주의 모든 Misskey 사용자를 위한 다양한 도구 모음집 🚀'; - const desc = d || '🌠 연합우주의 모든 Misskey 사용자를 위한 다양한 도구 모음집 🚀';
title= title title= title
meta(name='description' content=desc) meta(name='description' content=desc)
meta(property='og:title' content=title) meta(property='og:title' content=title)
meta(property='og:site_name' content='Misskey Tools with LycheeBridge') meta(property='og:site_name' content='Misskey Tools with LycheeBridge')
meta(property='og:description' content=desc) meta(property='og:description' content=desc)
meta(property='og:type' content='website') meta(property='og:type' content='website')
meta(property='og:image' content="https://tools.phater.live/assets/misskey.png") meta(property='og:image' content="https://tools.phater.live/assets/misskey.png")
link(rel="icon", href="/assets/lcb.png", type="image/png") link(rel="icon", href="/assets/lcb.png", type="image/png")
link(rel="preload", href="/assets/otadesign_rounded.woff") link(rel="preload", href="/assets/otadesign_rounded.woff")
link(rel="preload", href="/assets/otadesign_rounded.woff2") link(rel="preload", href="/assets/otadesign_rounded.woff2")
link(rel="preload", href="/assets/PretendardJPVariable.woff2") link(rel="preload", href="/assets/PretendardJPVariable.woff2")
script(src='https://kit.fontawesome.com/a4464bc6cd.js' crossorigin='anonymous') script(src='https://kit.fontawesome.com/a4464bc6cd.js' crossorigin='anonymous')
link(rel="stylesheet", href="/assets/style.css") link(rel="stylesheet", href="/assets/style.css")
body body
#app: .loading 불러오는 중입니다... #app: .loading 불러오는 중입니다...
if token if token
script. script.
const token = '#{token}'; const token = '#{token}';
const previousToken = localStorage.getItem('token'); const previousToken = localStorage.getItem('token');
const accounts = JSON.parse(localStorage.getItem('accounts') || '[]'); const accounts = JSON.parse(localStorage.getItem('accounts') || '[]');
if (previousToken && !accounts.includes(previousToken)) { if (previousToken && !accounts.includes(previousToken)) {
accounts.push(previousToken); accounts.push(previousToken);
} }
localStorage.setItem('accounts', JSON.stringify(accounts)); localStorage.setItem('accounts', JSON.stringify(accounts));
localStorage.setItem('token', token); localStorage.setItem('token', token);
history.replaceState(null, null, '/'); history.replaceState(null, null, '/');
if error if error
script. script.
window.__misshaialert = { error: '#{error}' }; window.__misshaialert = { error: '#{error}' };
script(src=`/assets/fe.${version}.js` async defer) script(src=`/assets/fe.${version}.js` async defer)

View file

@ -1,13 +1,13 @@
doctype html doctype html
html html
head head
meta(charset="UTF-8") meta(charset="UTF-8")
body body
p 클라이언트에 문제가 발생했습니다. 해결을 위해 브라우저에 있는 데이터를 제거했습니다. p 클라이언트에 문제가 발생했습니다. 해결을 위해 브라우저에 있는 데이터를 제거했습니다.
p 3초 뒤에 메인 페이지로 자동으로 돌아갑니다... p 3초 뒤에 메인 페이지로 자동으로 돌아갑니다...
hr hr
p There's a problem in client and deleted data on device to solve it. p There's a problem in client and deleted data on device to solve it.
p You will be redirected to the top in 3 seconds. p You will be redirected to the top in 3 seconds.
script. script.
localStorage.clear(); localStorage.clear();
setTimeout(() => location.href = '/', 3000); setTimeout(() => location.href = '/', 3000);

View file

@ -1,25 +1,11 @@
const allKatakana = [ const allTexts: string[] = [
...('アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨ'.split('')), '아무말 빔----',
'ウィ', 'ウェ', '휴대폰용 보험은 가입하셨나요?',
'キャ', 'キュ', 'キョ',
'クァ', 'クォ',
'シャ', 'シュ', 'ショ',
'チャ', 'チュ', 'チョ',
'ヒャ', 'ヒュ', 'ヒョ',
'ミャ', 'ミュ', 'ミョ'
]; ];
const allInfix = [ '', 'ー', 'ッ' ]; const getRandomText = () => allTexts[Math.floor(Math.random() * allTexts.length)];
const getRandomKatakana = () => allKatakana[Math.floor(Math.random() * allKatakana.length)];
const getRandomInfix = () => allInfix[Math.floor(Math.random() * allInfix.length)];
export const createGacha = () => { export const createGacha = () => {
return [ const result = getRandomText();
getRandomKatakana(), return result;
getRandomInfix(),
getRandomKatakana(),
getRandomInfix(),
...(new Array(Math.floor(Math.random() * 2 + 1)).fill('').map(() => getRandomKatakana()))
].join('');
}; };

View file

@ -47,7 +47,7 @@ export const format = (user: IUser, count: Count): string => {
return !v ? m : typeof v === 'function' ? v(score, user) : v; return !v ? m : typeof v === 'function' ? v(score, user) : v;
}); });
if (user.appendHashtag) { if (user.appendHashtag) {
result = result + '\n\n#misshaialert' result = result + '\n\n#misshaialert';
} }
return result; return result;
}; };

View file

@ -7,6 +7,7 @@ export const errorCodes = [
'notAuthorized', 'notAuthorized',
'hostNotFound', 'hostNotFound',
'invalidHostFormat', 'invalidHostFormat',
'noNewUserAllowed',
'other', 'other',
] as const; ] as const;

View file

@ -10,36 +10,36 @@ import { useSelector } from './store';
import { setDrawerShown } from './store/slices/screen'; import { setDrawerShown } from './store/slices/screen';
const Container = styled.div<IsMobileProp>` const Container = styled.div<IsMobileProp>`
padding: var(--margin); padding: var(--margin);
position: relative; position: relative;
`; `;
const Sidebar = styled.nav` const Sidebar = styled.nav`
width: 320px; width: 320px;
position: fixed; position: fixed;
top: var(--margin); top: var(--margin);
left: var(--margin); left: var(--margin);
`; `;
const Main = styled.main<IsMobileProp>` const Main = styled.main<IsMobileProp>`
flex: 1; flex: 1;
margin-top: 80px; margin-top: 80px;
margin-left: ${p => !p.isMobile ? `${320 + 16}px` : 0}; margin-left: ${p => !p.isMobile ? `${320 + 16}px` : 0};
min-width: 0; min-width: 0;
`; `;
const MobileHeader = styled.header` const MobileHeader = styled.header`
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
right: 0; right: 0;
height: 64px; height: 64px;
background: var(--panel); background: var(--panel);
z-index: 1000; z-index: 1000;
> h1 { > h1 {
font-size: 1rem; font-size: 1rem;
margin-bottom: 0; margin-bottom: 0;
} }
`; `;
export const GeneralLayout: React.FC = ({children}) => { export const GeneralLayout: React.FC = ({children}) => {

View file

@ -6,7 +6,7 @@ import { useGetSessionQuery } from './services/session';
import { useSelector } from './store'; import { useSelector } from './store';
export type HeaderProps = { export type HeaderProps = {
title?: string; title?: string;
}; };
export const Header: React.FC<HeaderProps> = ({title}) => { export const Header: React.FC<HeaderProps> = ({title}) => {

View file

@ -1,8 +1,8 @@
import React from 'react'; import React from 'react';
export type CardProps = { export type CardProps = {
className?: string; className?: string;
bodyClassName?: string; bodyClassName?: string;
}; };
export const Card: React.FC<CardProps> = ({children, className, bodyClassName}) => { export const Card: React.FC<CardProps> = ({children, className, bodyClassName}) => {

View file

@ -2,7 +2,7 @@ import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
export type HashtagTimelineProps = { export type HashtagTimelineProps = {
hashtag: string; hashtag: string;
}; };
export const HashtagTimeline: React.VFC<HashtagTimelineProps> = ({hashtag}) => { export const HashtagTimeline: React.VFC<HashtagTimelineProps> = ({hashtag}) => {

View file

@ -7,7 +7,7 @@ const LogItem: React.FC<{log: Log}> = ({log}) => {
return ( return (
<div className={`log ${log.level}`}> <div className={`log ${log.level}`}>
[{time}] {log.text} [{time}] {log.text}
</div> </div>
); );
}; };

View file

@ -3,8 +3,8 @@ import { useTranslation } from 'react-i18next';
import styled from 'styled-components'; import styled from 'styled-components';
const Input = styled.input` const Input = styled.input`
width: auto; width: auto;
flex: 1; flex: 1;
`; `;
export const LoginForm: React.VFC = () => { export const LoginForm: React.VFC = () => {

View file

@ -63,7 +63,7 @@ export const NavigationMenu: React.VFC = () => {
{meta && ( {meta && (
<section> <section>
<a className="item" href={CHANGELOG_URL} onClick={onClickItem}> <a className="item" href={CHANGELOG_URL} onClick={onClickItem}>
v{meta.version} {t('changelog')} v{meta.version} {t('changelog')}
</a> </a>
</section> </section>
)} )}

View file

@ -3,19 +3,19 @@ import { useTranslation } from 'react-i18next';
import { $get } from '../misc/api'; import { $get } from '../misc/api';
interface RankingResponse { interface RankingResponse {
isCalculating: boolean; isCalculating: boolean;
userCount: number; userCount: number;
ranking: Ranking[]; ranking: Ranking[];
} }
interface Ranking { interface Ranking {
username?: string; username?: string;
host?: string; host?: string;
rating: number; rating: number;
} }
export type RankingProps = { export type RankingProps = {
limit?: number; limit?: number;
}; };
export const Ranking: React.VFC<RankingProps> = ({limit}) => { export const Ranking: React.VFC<RankingProps> = ({limit}) => {
@ -60,7 +60,7 @@ export const Ranking: React.VFC<RankingProps> = ({limit}) => {
<div className="item flex" key={i}> <div className="item flex" key={i}>
<div className="text-bold pr-2">{i + 1}</div> <div className="text-bold pr-2">{i + 1}</div>
<div> <div>
-------------<br/> -------------<br/>
<span className="text-dimmed text-75">{t('_missHai.rating')}: {r.rating}</span> <span className="text-dimmed text-75">{t('_missHai.rating')}: {r.rating}</span>
</div> </div>
</div> </div>

View file

@ -1,8 +1,8 @@
import React from 'react'; import React from 'react';
export type SkeletonProps = { export type SkeletonProps = {
width?: string | number; width?: string | number;
height?: string | number; height?: string | number;
}; };
export const Skeleton: React.VFC<SkeletonProps> = (p) => { export const Skeleton: React.VFC<SkeletonProps> = (p) => {

View file

@ -1,15 +1,15 @@
import React from 'react'; import React from 'react';
export type TabItem = { export type TabItem = {
label: string; label: string;
key: string; key: string;
isNew?: boolean; isNew?: boolean;
}; };
export type TabProps = { export type TabProps = {
items: TabItem[]; items: TabItem[];
selected: string; selected: string;
onSelect: (key: string) => void; onSelect: (key: string) => void;
}; };
// タブコンポーネント // タブコンポーネント

View file

@ -50,7 +50,7 @@
"update": "Update", "update": "Update",
"shareMisskeyTools": "Share #MisskeyTools", "shareMisskeyTools": "Share #MisskeyTools",
"shareMisskeyToolsNote": "Try #MisskeyTools !\n\nhttps://t.psec.dev", "shareMisskeyToolsNote": "Try #MisskeyTools !\n\nhttps://t.psec.dev",
"instanceUrlPlaceholder": "e.g. misskey.io", "instanceUrlPlaceholder": "k.lapy.link, stella.place, psec.dev...",
"settings": "Settings", "settings": "Settings",
"accentColor": "Accent Color", "accentColor": "Accent Color",
"changelog": "Changelog", "changelog": "Changelog",
@ -156,7 +156,7 @@
} }
}, },
"_error": { "_error": {
"sorry": "Something went wrong. Please retry again.", "sorry": "We have some small problem. Please try again later.",
"additionalInfo": "Additional Info: ", "additionalInfo": "Additional Info: ",
"hitorisskeyIsDenied": "You cannot integrate with hitorisskey.", "hitorisskeyIsDenied": "You cannot integrate with hitorisskey.",
"teapot": "I'm a teapot.", "teapot": "I'm a teapot.",
@ -164,6 +164,7 @@
"tokenRequired": "Token is required.", "tokenRequired": "Token is required.",
"invalidParameter": "Invalid parameter.", "invalidParameter": "Invalid parameter.",
"notAuthorized": "Not authorized.", "notAuthorized": "Not authorized.",
"noNewUserAllowed": "You cannot signup to this service for now.",
"hostNotFound": "Could not connect to the instance. Make sure that the host name is correct and the instance is live.", "hostNotFound": "Could not connect to the instance. Make sure that the host name is correct and the instance is live.",
"other": "None" "other": "None"
}, },

View file

@ -10,19 +10,19 @@ const merge = (baseData: Record<string, unknown>, newData: Record<string, unknow
}); });
}; };
const _enUS = merge(jaJP, enUS); const _enUS = merge(koKR, enUS);
const _koKR = merge(_enUS, koKR) const _jaJP = merge(_enUS, jaJP);
export const resources = { export const resources = {
'ja_JP': { translation: jaJP }, 'ko_KR': { translation: koKR },
'en_US': { translation: _enUS }, 'en_US': { translation: _enUS },
'ko_KR': { translation: _koKR }, 'ja_JP': { translation: _jaJP },
}; };
export const languageName = { export const languageName = {
'ja_JP': '日本語',
'en_US': 'English',
'ko_KR': '한국어', 'ko_KR': '한국어',
'en_US': 'English',
'ja_JP': '日本語',
} as const; } as const;
export type LanguageCode = keyof typeof resources; export type LanguageCode = keyof typeof resources;

View file

@ -50,7 +50,7 @@
"update": "更新する", "update": "更新する",
"shareMisskeyTools": "#MisskeyTools をシェアする", "shareMisskeyTools": "#MisskeyTools をシェアする",
"shareMisskeyToolsNote": "#MisskeyTools はいいぞ\n\nhttps://t.psec.dev", "shareMisskeyToolsNote": "#MisskeyTools はいいぞ\n\nhttps://t.psec.dev",
"instanceUrlPlaceholder": "misskey.io", "instanceUrlPlaceholder": "k.lapy.link, stella.place, psec.dev...",
"settings": "設定", "settings": "設定",
"accentColor": "アクセントカラー", "accentColor": "アクセントカラー",
"changelog": "更新履歴", "changelog": "更新履歴",
@ -164,6 +164,7 @@
"tokenRequired": "トークンがありません。", "tokenRequired": "トークンがありません。",
"invalidParameter": "パラメータが不正です。", "invalidParameter": "パラメータが不正です。",
"notAuthorized": "権限がありません。", "notAuthorized": "権限がありません。",
"noNewUserAllowed": "現在、新規登録はできません。",
"hostNotFound": "インスタンスに接続できませんでした。ホスト名が正しく入力されているか、接続先のインスタンスが正常に動作しているかご確認ください。", "hostNotFound": "インスタンスに接続できませんでした。ホスト名が正しく入力されているか、接続先のインスタンスが正常に動作しているかご確認ください。",
"other": "不明なエラーです。" "other": "不明なエラーです。"
}, },

View file

@ -164,6 +164,7 @@
"tokenRequired": "잘못된 토큰이거나, 세션이 존재하지 않습니다.", "tokenRequired": "잘못된 토큰이거나, 세션이 존재하지 않습니다.",
"invalidParameter": "잘못된 요청 내용입니다. 값을 확인 후 다시 시도하세요.", "invalidParameter": "잘못된 요청 내용입니다. 값을 확인 후 다시 시도하세요.",
"notAuthorized": "이 리소스에 접근할 권한이 없습니다.", "notAuthorized": "이 리소스에 접근할 권한이 없습니다.",
"noNewUserAllowed": "현재 본 서비스에 신규 가입하실 수 없습니다.",
"hostNotFound": "인스턴스에 접속할 수 없습니다. 입력한 인스턴스 주소가 올바른지, API 요청이 가능한지 확인해주세요.", "hostNotFound": "인스턴스에 접속할 수 없습니다. 입력한 인스턴스 주소가 올바른지, API 요청이 가능한지 확인해주세요.",
"other": "알 수 없는 오류가 발생했습니다. Mastodon 인스턴스로 로그인을 시도했을 수 있습니다." "other": "알 수 없는 오류가 발생했습니다. Mastodon 인스턴스로 로그인을 시도했을 수 있습니다."
}, },

View file

@ -43,7 +43,7 @@ export const AccountsPage: React.VFC = () => {
accounts.map(account => ( accounts.map(account => (
<button className="item fluid" style={{display: 'flex', flexDirection: 'row', alignItems: 'center'}} onClick={() => switchAccount(account.misshaiToken)}> <button className="item fluid" style={{display: 'flex', flexDirection: 'row', alignItems: 'center'}} onClick={() => switchAccount(account.misshaiToken)}>
<i className="icon fas fa-chevron-right" /> <i className="icon fas fa-chevron-right" />
@{account.username}@{account.host} @{account.username}@{account.host}
<button className="btn flat text-danger" style={{marginLeft: 'auto'}} onClick={e => { <button className="btn flat text-danger" style={{marginLeft: 'auto'}} onClick={e => {
const filteredAccounts = accounts.filter(ac => ac.id !== account.id); const filteredAccounts = accounts.filter(ac => ac.id !== account.id);
dispatch(setAccounts(filteredAccounts)); dispatch(setAccounts(filteredAccounts));

View file

@ -85,9 +85,9 @@ export const AdminPage: React.VFC = () => {
}; };
/** /**
* Session APIのエラーハンドリング * Session APIのエラーハンドリング
* APIがエラーを返した = * APIがエラーを返した =
*/ */
useEffect(() => { useEffect(() => {
if (error) { if (error) {
console.error(error); console.error(error);
@ -97,8 +97,8 @@ export const AdminPage: React.VFC = () => {
}, [error]); }, [error]);
/** /**
* Edit ModeがオンのときDelete Modeを無効化する * Edit ModeがオンのときDelete Modeを無効化する
*/ */
useEffect(() => { useEffect(() => {
if (isEditMode) { if (isEditMode) {
setDeleteMode(false); setDeleteMode(false);
@ -106,8 +106,8 @@ export const AdminPage: React.VFC = () => {
}, [isEditMode]); }, [isEditMode]);
/** /**
* *
*/ */
useEffect(() => { useEffect(() => {
fetchAll(); fetchAll();
}, []); }, []);
@ -166,7 +166,7 @@ export const AdminPage: React.VFC = () => {
{!isDeleteMode && ( {!isDeleteMode && (
<button className="item fluid" onClick={() => setEditMode(true)}> <button className="item fluid" onClick={() => setEditMode(true)}>
<i className="icon fas fa-plus" /> <i className="icon fas fa-plus" />
</button> </button>
)} )}
</div> </div>
@ -174,22 +174,22 @@ export const AdminPage: React.VFC = () => {
) : ( ) : (
<div className="vstack"> <div className="vstack">
<label className="input-field"> <label className="input-field">
<input type="text" value={draftTitle} onChange={e => setDraftTitle(e.target.value)} /> <input type="text" value={draftTitle} onChange={e => setDraftTitle(e.target.value)} />
</label> </label>
<label className="input-field"> <label className="input-field">
<textarea className="input-field" value={draftBody} rows={10} onChange={e => setDraftBody(e.target.value)}/> <textarea className="input-field" value={draftBody} rows={10} onChange={e => setDraftBody(e.target.value)}/>
</label> </label>
<div className="hstack" style={{justifyContent: 'flex-end'}}> <div className="hstack" style={{justifyContent: 'flex-end'}}>
<button className="btn primary" onClick={submitAnnouncement} disabled={!draftTitle || !draftBody}> <button className="btn primary" onClick={submitAnnouncement} disabled={!draftTitle || !draftBody}>
</button> </button>
<button className="btn" onClick={() => { <button className="btn" onClick={() => {
selectAnnouncement(null); selectAnnouncement(null);
setEditMode(false); setEditMode(false);
}}> }}>
</button> </button>
</div> </div>
</div> </div>
@ -199,7 +199,7 @@ export const AdminPage: React.VFC = () => {
<h2> </h2> <h2> </h2>
<div className="vstack"> <div className="vstack">
<button className="btn danger" onClick={onClickStartMisshaiAlertWorkerButton}> <button className="btn danger" onClick={onClickStartMisshaiAlertWorkerButton}>
Misskey Tools Misskey Tools
</button> </button>
<h3> </h3> <h3> </h3>
{misshaiLog && <LogView log={misshaiLog} />} {misshaiLog && <LogView log={misshaiLog} />}

View file

@ -31,13 +31,13 @@ const variables = [
] as const; ] as const;
type SettingDraftType = Partial<Pick<IUser, type SettingDraftType = Partial<Pick<IUser,
| 'alertMode' | 'alertMode'
| 'visibility' | 'visibility'
| 'localOnly' | 'localOnly'
| 'remoteFollowersOnly' | 'remoteFollowersOnly'
| 'template' | 'template'
| 'useRanking' | 'useRanking'
| 'appendHashtag' | 'appendHashtag'
>>; >>;
type DraftReducer = React.Reducer<SettingDraftType, Partial<SettingDraftType>>; type DraftReducer = React.Reducer<SettingDraftType, Partial<SettingDraftType>>;
@ -169,9 +169,9 @@ export const MisshaiPage: React.VFC = () => {
}, [dispatch, t]); }, [dispatch, t]);
/** /**
* Session APIのエラーハンドリング * Session APIのエラーハンドリング
* APIがエラーを返した = * APIがエラーを返した =
*/ */
useEffect(() => { useEffect(() => {
if (session.error) { if (session.error) {
console.error(session.error); console.error(session.error);

View file

@ -51,7 +51,7 @@ export const IndexSessionPage: React.VFC = () => {
<td>{score.data?.followersCount ?? '...'}</td> <td>{score.data?.followersCount ?? '...'}</td>
<td>{score.data?.followersDelta ?? '...'}</td> <td>{score.data?.followersDelta ?? '...'}</td>
</tr> </tr>
<tr> <tr>
<td>{t('_missHai.rating')}</td> <td>{t('_missHai.rating')}</td>
<td>{session?.rating ?? '...'}</td> <td>{session?.rating ?? '...'}</td>
</tr> </tr>
@ -64,7 +64,7 @@ export const IndexSessionPage: React.VFC = () => {
<div className="menu large"> <div className="menu large">
<a className="item" href="https://psec.dev/@PSEC" target="_blank" rel="noopener noreferrer"> <a className="item" href="https://psec.dev/@PSEC" target="_blank" rel="noopener noreferrer">
<i className="icon fas fa-at"></i> <i className="icon fas fa-at"></i>
@PSEC@psec.dev @PSEC@psec.dev
</a> </a>
</div> </div>
</section> </section>

View file

@ -10,60 +10,60 @@ import Twemoji from 'react-twemoji';
import { useAnnouncements } from '../hooks/useAnnouncements'; import { useAnnouncements } from '../hooks/useAnnouncements';
const Hero = styled.div<IsMobileProp>` const Hero = styled.div<IsMobileProp>`
display: flex; display: flex;
position: relative; position: relative;
background: linear-gradient(-135deg, rgb(1, 169, 46), rgb(134, 179, 0) 35%); background: linear-gradient(-135deg, rgb(1, 169, 46), rgb(134, 179, 0) 35%);
color: var(--white); color: var(--white);
padding: ${f => f.isMobile ? '16px' : '60px 90px'}; padding: ${f => f.isMobile ? '16px' : '60px 90px'};
overflow: hidden; overflow: hidden;
gap: var(--margin); gap: var(--margin);
> .hero { > .hero {
flex: 2; flex: 2;
min-width: 0; min-width: 0;
position: relative; position: relative;
z-index: 1000; z-index: 1000;
p { p {
${f => f.isMobile ? 'font-size: 1rem;' : ''} ${f => f.isMobile ? 'font-size: 1rem;' : ''}
} }
} }
> .announcements { > .announcements {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
max-height: 512px; max-height: 512px;
overflow: auto; overflow: auto;
padding: var(--margin); padding: var(--margin);
border-radius: var(--radius); border-radius: var(--radius);
background: var(--black-50); background: var(--black-50);
backdrop-filter: blur(4px) brightness(120%); backdrop-filter: blur(4px) brightness(120%);
z-index: 1000; z-index: 1000;
@media screen and (max-width: 800px) { @media screen and (max-width: 800px) {
display: none; display: none;
} }
} }
> .rects { > .rects {
position: absolute; position: absolute;
display: grid; display: grid;
right: 160px; right: 160px;
bottom: -120px; bottom: -120px;
width: 400px; width: 400px;
height: 400px; height: 400px;
gap: 8px; gap: 8px;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr; grid-template-rows: 1fr 1fr;
transform-origin: center center; transform-origin: center center;
transform: rotate(45deg); transform: rotate(45deg);
opacity: 0.5; opacity: 0.5;
> .rect { > .rect {
border: 2px solid var(--white); border: 2px solid var(--white);
border-radius: 24px; border-radius: 24px;
box-shadow: 0 2px 4px var(--shadow-color); box-shadow: 0 2px 4px var(--shadow-color);
} }
} }
`; `;
const FormWrapper = styled.div` const FormWrapper = styled.div`
max-width: 500px; max-width: 500px;
color: var(--fg); color: var(--fg);
`; `;
export const IndexWelcomePage: React.VFC = () => { export const IndexWelcomePage: React.VFC = () => {
@ -102,7 +102,7 @@ export const IndexWelcomePage: React.VFC = () => {
</Hero> </Hero>
<Twemoji options={{className: 'twemoji'}}> <Twemoji options={{className: 'twemoji'}}>
<div className="py-4 text-125 text-center"> <div className="py-4 text-125 text-center">
👍&emsp;&emsp;😆&emsp;🎉&emsp;🍮 👍&emsp;&emsp;😆&emsp;🎉&emsp;🍮
</div> </div>
</Twemoji> </Twemoji>
<article className="xarticle vstack pa-2"> <article className="xarticle vstack pa-2">

View file

@ -14,23 +14,23 @@ import { designSystemColors } from '../../common/types/design-system-color';
import styled from 'styled-components'; import styled from 'styled-components';
const ColorInput = styled.input<{color: string}>` const ColorInput = styled.input<{color: string}>`
display: block; display: block;
appearance: none; appearance: none;
width: 32px; width: 32px;
height: 32px; height: 32px;
border-radius: 999px; border-radius: 999px;
background-color: var(--panel); background-color: var(--panel);
border: 4px solid var(--${p => p.color}); border: 4px solid var(--${p => p.color});
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.2s ease;
&:checked { &:checked {
background: var(--${p => p.color}); background: var(--${p => p.color});
cursor: default; cursor: default;
} }
&:hover, &:focus { &:hover, &:focus {
box-shadow: 0 0 16px var(--${p => p.color}); box-shadow: 0 0 16px var(--${p => p.color});
outline: none; outline: none;
} }
`; `;
export const SettingPage: React.VFC = () => { export const SettingPage: React.VFC = () => {

View file

@ -1,13 +1,13 @@
@font-face { @font-face {
font-family: 'Pretendard JP Variable'; font-family: 'Pretendard JP Variable';
font-weight: 45 920; font-weight: 45 920;
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
src: url("/assets/PretendardJPVariable.woff2") format('woff2-variations'); src: url("/assets/PretendardJPVariable.woff2") format('woff2-variations');
} }
html { html {
font-size: 16px; font-size: 16px;
} }
body { body {

View file

@ -1,9 +1,9 @@
@font-face { @font-face {
font-family: 'Pretendard JP Variable'; font-family: 'Pretendard JP Variable';
font-weight: 45 920; font-weight: 45 920;
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
src: url("/assets/PretendardJPVariable.woff2") format('woff2-variations'); src: url("/assets/PretendardJPVariable.woff2") format('woff2-variations');
} }
@font-face { @font-face {

View file

@ -8,74 +8,74 @@ const isProduction = process.env.NODE_ENV === 'production';
const meta = require('./package.json'); const meta = require('./package.json');
module.exports = { module.exports = {
entry: { entry: {
fe: './src/frontend/init.tsx', fe: './src/frontend/init.tsx',
}, },
module: { module: {
rules: [{ rules: [{
test: /\.(eot|woff|woff2|svg|ttf)([?]?.*)$/, test: /\.(eot|woff|woff2|svg|ttf)([?]?.*)$/,
type: 'asset/resource' type: 'asset/resource'
}, { }, {
test: /\.json5$/, test: /\.json5$/,
loader: 'json5-loader', loader: 'json5-loader',
options: { options: {
esModule: false, esModule: false,
}, },
type: 'javascript/auto' type: 'javascript/auto'
}, { }, {
test: /\.tsx?$/, test: /\.tsx?$/,
use: [ use: [
{ loader: 'ts-loader' } { loader: 'ts-loader' }
] ]
}, { }, {
test: /\.scss$/, test: /\.scss$/,
use: [ use: [
'style-loader', 'style-loader',
{ {
loader: 'css-loader', loader: 'css-loader',
options: { options: {
url: false, url: false,
sourceMap: true, sourceMap: true,
}, },
}, },
{ {
loader: 'sass-loader', loader: 'sass-loader',
options: { options: {
implementation: require('sass'), implementation: require('sass'),
sassOptions: { sassOptions: {
fiber: false fiber: false
}, },
sourceMap: true, sourceMap: true,
}, },
}, },
], ],
}, { }, {
test: /\.css$/i, test: /\.css$/i,
use: ['style-loader', 'css-loader'], use: ['style-loader', 'css-loader'],
}] }]
}, },
plugins: [ plugins: [
new webpack.ProgressPlugin({}), new webpack.ProgressPlugin({}),
], ],
output: { output: {
path: __dirname + '/built/assets', path: __dirname + '/built/assets',
filename: `[name].${meta.version}.js`, filename: `[name].${meta.version}.js`,
publicPath: '/assets/', publicPath: '/assets/',
pathinfo: false, pathinfo: false,
}, },
resolve: { resolve: {
extensions: [ extensions: [
'.js', '.ts', '.json', '.tsx' '.js', '.ts', '.json', '.tsx'
], ],
alias: { alias: {
} }
}, },
resolveLoader: { resolveLoader: {
modules: ['node_modules'] modules: ['node_modules']
}, },
experiments: { experiments: {
topLevelAwait: true topLevelAwait: true
}, },
devtool: false, //'source-map', devtool: false, //'source-map',
mode: isProduction ? 'production' : 'development' mode: isProduction ? 'production' : 'development'
}; };