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:
parent
320dfc0696
commit
b6a3b0cd53
35 changed files with 335 additions and 367 deletions
|
@ -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
|
|
@ -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'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -24,5 +24,5 @@ export class UserSetting {
|
||||||
useRanking?: boolean;
|
useRanking?: boolean;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
appendHashtag?: boolean;
|
appendHashtag?: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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}명의 알림 전송이 완료되었습니다.`);
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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('');
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,6 +7,7 @@ export const errorCodes = [
|
||||||
'notAuthorized',
|
'notAuthorized',
|
||||||
'hostNotFound',
|
'hostNotFound',
|
||||||
'invalidHostFormat',
|
'invalidHostFormat',
|
||||||
|
'noNewUserAllowed',
|
||||||
'other',
|
'other',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
|
|
@ -5,4 +5,4 @@ export const visibilities = [
|
||||||
'users' // ログインユーザー (Groundpolis 限定)
|
'users' // ログインユーザー (Groundpolis 限定)
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type Visibility = typeof visibilities[number];
|
export type Visibility = typeof visibilities[number];
|
||||||
|
|
|
@ -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}) => {
|
||||||
|
|
|
@ -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}) => {
|
||||||
|
|
|
@ -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}) => {
|
||||||
|
|
|
@ -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}) => {
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 = () => {
|
||||||
|
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
// タブコンポーネント
|
// タブコンポーネント
|
||||||
|
|
|
@ -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"
|
||||||
},
|
},
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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": "不明なエラーです。"
|
||||||
},
|
},
|
||||||
|
|
|
@ -164,6 +164,7 @@
|
||||||
"tokenRequired": "잘못된 토큰이거나, 세션이 존재하지 않습니다.",
|
"tokenRequired": "잘못된 토큰이거나, 세션이 존재하지 않습니다.",
|
||||||
"invalidParameter": "잘못된 요청 내용입니다. 값을 확인 후 다시 시도하세요.",
|
"invalidParameter": "잘못된 요청 내용입니다. 값을 확인 후 다시 시도하세요.",
|
||||||
"notAuthorized": "이 리소스에 접근할 권한이 없습니다.",
|
"notAuthorized": "이 리소스에 접근할 권한이 없습니다.",
|
||||||
|
"noNewUserAllowed": "현재 본 서비스에 신규 가입하실 수 없습니다.",
|
||||||
"hostNotFound": "인스턴스에 접속할 수 없습니다. 입력한 인스턴스 주소가 올바른지, API 요청이 가능한지 확인해주세요.",
|
"hostNotFound": "인스턴스에 접속할 수 없습니다. 입력한 인스턴스 주소가 올바른지, API 요청이 가능한지 확인해주세요.",
|
||||||
"other": "알 수 없는 오류가 발생했습니다. Mastodon 인스턴스로 로그인을 시도했을 수 있습니다."
|
"other": "알 수 없는 오류가 발생했습니다. Mastodon 인스턴스로 로그인을 시도했을 수 있습니다."
|
||||||
},
|
},
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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} />}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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">
|
||||||
👍 ❤ 😆 🎉 🍮
|
👍 ❤ 😆 🎉 🍮
|
||||||
</div>
|
</div>
|
||||||
</Twemoji>
|
</Twemoji>
|
||||||
<article className="xarticle vstack pa-2">
|
<article className="xarticle vstack pa-2">
|
||||||
|
|
|
@ -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 = () => {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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'
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue