wip
This commit is contained in:
parent
d06f2384dc
commit
230e952c84
19 changed files with 312 additions and 115 deletions
|
@ -25,6 +25,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/preset-react": "^7.14.5",
|
"@babel/preset-react": "^7.14.5",
|
||||||
"@reduxjs/toolkit": "^1.6.1",
|
"@reduxjs/toolkit": "^1.6.1",
|
||||||
|
"@types/insert-text-at-cursor": "^0.3.0",
|
||||||
"@types/koa-bodyparser": "^4.3.0",
|
"@types/koa-bodyparser": "^4.3.0",
|
||||||
"@types/koa-multer": "^1.0.0",
|
"@types/koa-multer": "^1.0.0",
|
||||||
"@types/koa-send": "^4.1.3",
|
"@types/koa-send": "^4.1.3",
|
||||||
|
@ -45,6 +46,7 @@
|
||||||
"fibers": "^5.0.0",
|
"fibers": "^5.0.0",
|
||||||
"i18next": "^20.6.1",
|
"i18next": "^20.6.1",
|
||||||
"i18next-browser-languagedetector": "^6.1.2",
|
"i18next-browser-languagedetector": "^6.1.2",
|
||||||
|
"insert-text-at-cursor": "^0.3.0",
|
||||||
"json5-loader": "^4.0.1",
|
"json5-loader": "^4.0.1",
|
||||||
"koa": "^2.13.0",
|
"koa": "^2.13.0",
|
||||||
"koa-bodyparser": "^4.3.0",
|
"koa-bodyparser": "^4.3.0",
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React, { useState, useEffect, useCallback } from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import { BrowserRouter, Link, Route, Switch, useLocation } from 'react-router-dom';
|
import { BrowserRouter, Link, Route, Switch, useLocation } from 'react-router-dom';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { IndexPage } from './pages';
|
import { IndexPage } from './pages';
|
||||||
import { RankingPage } from './pages/ranking';
|
import { RankingPage } from './pages/ranking';
|
||||||
|
@ -46,6 +47,8 @@ const AppInner : React.VFC = () => {
|
||||||
};
|
};
|
||||||
}, [osTheme, setOsTheme]);
|
}, [osTheme, setOsTheme]);
|
||||||
|
|
||||||
|
const {t} = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="container">
|
<div className="container">
|
||||||
|
@ -57,7 +60,7 @@ const AppInner : React.VFC = () => {
|
||||||
</Switch>
|
</Switch>
|
||||||
<footer className="text-center pa-5">
|
<footer className="text-center pa-5">
|
||||||
<p>(C)2020-2021 Xeltica</p>
|
<p>(C)2020-2021 Xeltica</p>
|
||||||
<p><Link to="/term">利用規約</Link></p>
|
<p><Link to="/term">{t('termsOfService')}</Link></p>
|
||||||
</footer>
|
</footer>
|
||||||
<ModalComponent />
|
<ModalComponent />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -73,7 +73,7 @@ export const ModalComponent: React.VFC = () => {
|
||||||
if (!shown || !modal) return null;
|
if (!shown || !modal) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="modal" onClick={() => dispatch(hideModal())}>
|
<div className="modal fade" onClick={() => dispatch(hideModal())}>
|
||||||
<div className="fade up" onClick={(e) => e.stopPropagation()}>
|
<div className="fade up" onClick={(e) => e.stopPropagation()}>
|
||||||
{ ModalInner(modal) }
|
{ ModalInner(modal) }
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export const DeveloperInfo: React.VFC = () => {
|
export const DeveloperInfo: React.VFC = () => {
|
||||||
|
const {t} = useTranslation();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1>開発者</h1>
|
<h1>{t('_developerInfo.title')}</h1>
|
||||||
<p>何か困ったことがあったら、以下のアカウントにメッセージを送ってください。</p>
|
<p>{t('_developerInfo.description')}</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="http://misskey.io/@ebi" target="_blank" rel="noopener noreferrer">@ebi@misskey.io</a></li>
|
<li><a href="http://misskey.io/@ebi" target="_blank" rel="noopener noreferrer">@ebi@misskey.io</a></li>
|
||||||
<li><a href="http://groundpolis.app/@X" target="_blank" rel="noopener noreferrer">@X@groundpolis.app</a></li>
|
<li><a href="http://groundpolis.app/@X" target="_blank" rel="noopener noreferrer">@X@groundpolis.app</a></li>
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
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}) => {
|
||||||
|
const {t} = useTranslation();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1>タイムライン</h1>
|
<h1>{t('_timeline.title')}</h1>
|
||||||
<p>#{hashtag} タグを含む最新ノートを表示します。</p>
|
<p>{t('_timeline.description', { hashtag })}</p>
|
||||||
<p>WIP</p>
|
<p>WIP</p>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,19 +8,17 @@ export type HeaderProps = {
|
||||||
hasTopLink?: boolean;
|
hasTopLink?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const messageNumber = Math.floor(Math.random() * 6) + 1;
|
||||||
|
|
||||||
export const Header: React.FC<HeaderProps> = ({hasTopLink, children}) => {
|
export const Header: React.FC<HeaderProps> = ({hasTopLink, children}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const message = React.useMemo(
|
|
||||||
() => welcomeMessage[Math.floor(Math.random() * welcomeMessage.length)] , []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className={'xarticle card shadow-4 mt-5 mb-3'}>
|
<header className={'xarticle card mt-5 mb-3'}>
|
||||||
<div className="body">
|
<div className="body">
|
||||||
<h1 className="text-primary mb-0" style={{ fontSize: '2rem' }}>
|
<h1 className="text-primary mb-0" style={{ fontSize: '2rem' }}>
|
||||||
{hasTopLink ? <Link to="/">{t('title')}</Link> : t('title')}
|
{hasTopLink ? <Link to="/">{t('title')}</Link> : t('title')}
|
||||||
</h1>
|
</h1>
|
||||||
<h2 className="text-dimmed ml-1">{message}</h2>
|
<h2 className="text-dimmed ml-1">{t(`_welcomeMessage.pattern${messageNumber}`)}</h2>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export const LoginForm: React.VFC = () => {
|
export const LoginForm: React.VFC = () => {
|
||||||
const [host, setHost] = useState('');
|
const [host, setHost] = useState('');
|
||||||
|
const {t} = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav>
|
<nav>
|
||||||
<div>
|
<div>
|
||||||
<strong>インスタンスURL</strong>
|
<strong>{t('instanceUrl')}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div className="hgroup">
|
<div className="hgroup">
|
||||||
<input
|
<input
|
||||||
className="input-field"
|
className="input-field"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="例: misskey.io"
|
|
||||||
value={host}
|
value={host}
|
||||||
onChange={(e) => setHost(e.target.value)}
|
onChange={(e) => setHost(e.target.value)}
|
||||||
required
|
required
|
||||||
|
@ -23,7 +24,7 @@ export const LoginForm: React.VFC = () => {
|
||||||
disabled={!host}
|
disabled={!host}
|
||||||
onClick={() => location.href = `//${location.host}/login?host=${encodeURIComponent(host)}`}
|
onClick={() => location.href = `//${location.host}/login?host=${encodeURIComponent(host)}`}
|
||||||
>
|
>
|
||||||
ログイン
|
{t('login')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
interface RankingResponse {
|
interface RankingResponse {
|
||||||
isCalculating: boolean;
|
isCalculating: boolean;
|
||||||
|
@ -21,6 +22,7 @@ export const Ranking: React.VFC<RankingProps> = ({limit}) => {
|
||||||
const [response, setResponse] = useState<RankingResponse | null>(null);
|
const [response, setResponse] = useState<RankingResponse | null>(null);
|
||||||
const [isFetching, setIsFetching] = useState(true);
|
const [isFetching, setIsFetching] = useState(true);
|
||||||
const [isError, setIsError] = useState(false);
|
const [isError, setIsError] = useState(false);
|
||||||
|
const {t} = useTranslation();
|
||||||
|
|
||||||
// APIコール
|
// APIコール
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -39,27 +41,27 @@ export const Ranking: React.VFC<RankingProps> = ({limit}) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
isFetching ? (
|
isFetching ? (
|
||||||
<p className="text-dimmed">取得中…</p>
|
<p className="text-dimmed">{t('fetching')}</p>
|
||||||
) : isError ? (
|
) : isError ? (
|
||||||
<div className="alert bg-danger">取得エラー</div>
|
<div className="alert bg-danger">{t('failedToFetch')}</div>
|
||||||
) : response ? (
|
) : response ? (
|
||||||
<>
|
<>
|
||||||
<aside>登録者数:{response?.userCount}</aside>
|
<aside>{t('registeredUsersCount')}: {response?.userCount}</aside>
|
||||||
{response.isCalculating ? (
|
{response.isCalculating ? (
|
||||||
<p>現在算出中です。後ほどご確認ください!</p>
|
<p>{t('isCalculating')}</p>
|
||||||
) : (
|
) : (
|
||||||
<table className="table shadow-2 mt-1 fluid">
|
<table className="table mt-1 fluid">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>順位</th>
|
<th>{t('_missHai.order')}</th>
|
||||||
<th>名前</th>
|
<th>{t('name')}</th>
|
||||||
<th>レート</th>
|
<th>{t('_missHai.rating')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{response.ranking.map((r, i) => (
|
{response.ranking.map((r, i) => (
|
||||||
<tr key={i}>
|
<tr key={i}>
|
||||||
<td>{i + 1}位</td>
|
<td>{i + 1}</td>
|
||||||
<td>
|
<td>
|
||||||
{r.username}@{r.host}
|
{r.username}@{r.host}
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { LOCALSTORAGE_KEY_TOKEN } from '../const';
|
import { LOCALSTORAGE_KEY_TOKEN } from '../const';
|
||||||
import { useGetScoreQuery, useGetSessionQuery } from '../services/session';
|
import { useGetScoreQuery, useGetSessionQuery } from '../services/session';
|
||||||
import { Skeleton } from './Skeleton';
|
import { Skeleton } from './Skeleton';
|
||||||
|
@ -6,6 +7,7 @@ import { Skeleton } from './Skeleton';
|
||||||
export const SessionDataPage: React.VFC = () => {
|
export const SessionDataPage: React.VFC = () => {
|
||||||
const session = useGetSessionQuery(undefined);
|
const session = useGetSessionQuery(undefined);
|
||||||
const score = useGetScoreQuery(undefined);
|
const score = useGetScoreQuery(undefined);
|
||||||
|
const {t} = useTranslation();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Session APIのエラーハンドリング
|
* Session APIのエラーハンドリング
|
||||||
|
@ -30,20 +32,10 @@ export const SessionDataPage: React.VFC = () => {
|
||||||
<div className="fade">
|
<div className="fade">
|
||||||
{session.data && (
|
{session.data && (
|
||||||
<section>
|
<section>
|
||||||
<p>
|
<p>{t('welcomeBack', {acct: `@${session.data.username}@${session.data.host}`})}</p>
|
||||||
おかえりなさい、
|
|
||||||
<a
|
|
||||||
href={`https://${session.data.host}/@${session.data.username}`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer noopener"
|
|
||||||
>
|
|
||||||
@{session.data.username}@{session.data.host}
|
|
||||||
</a>
|
|
||||||
さん。
|
|
||||||
</p>
|
|
||||||
<p>
|
<p>
|
||||||
<strong>
|
<strong>
|
||||||
みす廃レート:
|
{t('_missHai.rating')}:
|
||||||
</strong>
|
</strong>
|
||||||
{session.data.rating}
|
{session.data.rating}
|
||||||
</p>
|
</p>
|
||||||
|
@ -51,28 +43,28 @@ export const SessionDataPage: React.VFC = () => {
|
||||||
)}
|
)}
|
||||||
{score.data && (
|
{score.data && (
|
||||||
<section>
|
<section>
|
||||||
<h2>みす廃データ</h2>
|
<h2>{t('_missHai.data')}</h2>
|
||||||
<table className="table fluid shadow-2" style={{border: 'none'}}>
|
<table className="table fluid">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>内容</th>
|
<th></th>
|
||||||
<th>スコア</th>
|
<th>{t('_missHai.dataScore')}</th>
|
||||||
<th>前日比</th>
|
<th>{t('_missHai.dataDelta')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>ノート</td>
|
<td>{t('notes')}</td>
|
||||||
<td>{score.data.notesCount}</td>
|
<td>{score.data.notesCount}</td>
|
||||||
<td>{score.data.notesDelta}</td>
|
<td>{score.data.notesDelta}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>フォロー</td>
|
<td>{t('following')}</td>
|
||||||
<td>{score.data.followingCount}</td>
|
<td>{score.data.followingCount}</td>
|
||||||
<td>{score.data.followingDelta}</td>
|
<td>{score.data.followingDelta}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>フォロワー</td>
|
<td>{t('followers')}</td>
|
||||||
<td>{score.data.followersCount}</td>
|
<td>{score.data.followersCount}</td>
|
||||||
<td>{score.data.followersDelta}</td>
|
<td>{score.data.followersDelta}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -5,12 +5,13 @@ import { Visibility } from '../../common/types/visibility';
|
||||||
import { useGetSessionQuery } from '../services/session';
|
import { useGetSessionQuery } from '../services/session';
|
||||||
import { defaultTemplate } from '../../common/default-template';
|
import { defaultTemplate } from '../../common/default-template';
|
||||||
import { Card } from './Card';
|
import { Card } from './Card';
|
||||||
import { Theme } from '../misc/theme';
|
import { Theme, themes } from '../misc/theme';
|
||||||
import { API_ENDPOINT, LOCALSTORAGE_KEY_LANG, LOCALSTORAGE_KEY_TOKEN } from '../const';
|
import { API_ENDPOINT, LOCALSTORAGE_KEY_TOKEN } from '../const';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { changeLang, changeTheme, showModal } from '../store/slices/screen';
|
import { changeLang, changeTheme, showModal } from '../store/slices/screen';
|
||||||
import { useSelector } from '../store';
|
import { useSelector } from '../store';
|
||||||
import { languageName } from '../langs';
|
import { languageName } from '../langs';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type SettingDraftType = Partial<Pick<IUser,
|
type SettingDraftType = Partial<Pick<IUser,
|
||||||
| 'alertMode'
|
| 'alertMode'
|
||||||
|
@ -27,6 +28,7 @@ export const SettingPage: React.VFC = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const data = session.data;
|
const data = session.data;
|
||||||
|
const {t} = useTranslation();
|
||||||
|
|
||||||
const [draft, dispatchDraft] = useReducer<DraftReducer>((state, action) => {
|
const [draft, dispatchDraft] = useReducer<DraftReducer>((state, action) => {
|
||||||
return { ...state, ...action };
|
return { ...state, ...action };
|
||||||
|
@ -38,23 +40,8 @@ export const SettingPage: React.VFC = () => {
|
||||||
template: data?.template ?? null,
|
template: data?.template ?? null,
|
||||||
});
|
});
|
||||||
|
|
||||||
const themes: Array<{ theme: Theme, name: string }> = [
|
|
||||||
{
|
|
||||||
theme: 'light',
|
|
||||||
name: 'ライトテーマ'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
theme: 'dark',
|
|
||||||
name: 'ダークテーマ'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
theme: 'system',
|
|
||||||
name: 'システム設定に準じる'
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const currentTheme = useSelector(state => state.screen.theme);
|
const currentTheme = useSelector(state => state.screen.theme);
|
||||||
const currentLang = useSelector(state => state.screen.lang);
|
const currentLang = useSelector(state => state.screen.language);
|
||||||
|
|
||||||
const availableVisibilities: Visibility[] = [
|
const availableVisibilities: Visibility[] = [
|
||||||
'public',
|
'public',
|
||||||
|
@ -150,7 +137,7 @@ export const SettingPage: React.VFC = () => {
|
||||||
) : (
|
) : (
|
||||||
<div className="vstack fade">
|
<div className="vstack fade">
|
||||||
<Card bodyClassName="vstack">
|
<Card bodyClassName="vstack">
|
||||||
<h1>アラート送信方法</h1>
|
<h1>{t('alertMode')}</h1>
|
||||||
<div>
|
<div>
|
||||||
{
|
{
|
||||||
alertModes.map((mode) => (
|
alertModes.map((mode) => (
|
||||||
|
@ -158,27 +145,27 @@ export const SettingPage: React.VFC = () => {
|
||||||
<input type="radio" checked={mode === draft.alertMode} onChange={() => {
|
<input type="radio" checked={mode === draft.alertMode} onChange={() => {
|
||||||
updateSetting({ alertMode: mode });
|
updateSetting({ alertMode: mode });
|
||||||
}} />
|
}} />
|
||||||
<span>{mode}</span>
|
<span>{t(`_alertMode.${mode}`)}</span>
|
||||||
</label>
|
</label>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
{draft.alertMode === 'notification' && (
|
{draft.alertMode === 'notification' && (
|
||||||
<div className="alert bg-danger mt-2">
|
<div className="alert bg-danger mt-2">
|
||||||
<i className="icon bi bi-exclamation-circle"></i>
|
<i className="icon bi bi-exclamation-circle"></i>
|
||||||
「Misskey に通知」オプションは古いMisskeyでは動作しません。
|
{t('_alertMode.notificationWarning')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{ draft.alertMode === 'note' && (
|
{ draft.alertMode === 'note' && (
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="visibility" className="input-field">公開範囲</label>
|
<label htmlFor="visibility" className="input-field">{t('visibility')}</label>
|
||||||
{
|
{
|
||||||
availableVisibilities.map((visibility) => (
|
availableVisibilities.map((visibility) => (
|
||||||
<label key={visibility} className="input-check">
|
<label key={visibility} className="input-check">
|
||||||
<input type="radio" checked={visibility === draft.visibility} onChange={() => {
|
<input type="radio" checked={visibility === draft.visibility} onChange={() => {
|
||||||
updateSetting({ visibility });
|
updateSetting({ visibility });
|
||||||
}} />
|
}} />
|
||||||
<span>{visibility}</span>
|
<span>{t(`_visibility.${visibility}`)}</span>
|
||||||
</label>
|
</label>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -186,26 +173,26 @@ export const SettingPage: React.VFC = () => {
|
||||||
<input type="checkbox" checked={draft.localOnly} onChange={(e) => {
|
<input type="checkbox" checked={draft.localOnly} onChange={(e) => {
|
||||||
updateSetting({ localOnly: e.target.checked });
|
updateSetting({ localOnly: e.target.checked });
|
||||||
}} />
|
}} />
|
||||||
<span>ローカル限定</span>
|
<span>{t('localOnly')}</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
<Card bodyClassName="vstack">
|
<Card bodyClassName="vstack">
|
||||||
<h1>表示設定</h1>
|
<h1>{t('appearance')}</h1>
|
||||||
<h2>テーマ</h2>
|
<h2>{t('theme')}</h2>
|
||||||
<div>
|
<div>
|
||||||
{
|
{
|
||||||
themes.map(({ theme, name }) => (
|
themes.map(theme => (
|
||||||
<label key={theme} className="input-check">
|
<label key={theme} className="input-check">
|
||||||
<input type="radio" value={theme} checked={theme === currentTheme} onChange={(e) => dispatch(changeTheme(e.target.value as Theme))} />
|
<input type="radio" value={theme} checked={theme === currentTheme} onChange={(e) => dispatch(changeTheme(e.target.value as Theme))} />
|
||||||
<span>{name}</span>
|
<span>{t(`_themes.${theme}`)}</span>
|
||||||
</label>
|
</label>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2>言語設定</h2>
|
<h2>{t('language')}</h2>
|
||||||
<select name="currentLang" className="input-field" value={currentLang} onChange={(e) => {
|
<select name="currentLang" className="input-field" value={currentLang} onChange={(e) => {
|
||||||
dispatch(changeLang(e.target.value));
|
dispatch(changeLang(e.target.value));
|
||||||
}}>
|
}}>
|
||||||
|
@ -218,16 +205,14 @@ export const SettingPage: React.VFC = () => {
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card bodyClassName="vstack">
|
<Card bodyClassName="vstack">
|
||||||
<h1>テンプレート</h1>
|
<h1>{t('template')}</h1>
|
||||||
<p>アラートの自動投稿をカスタマイズできます。</p>
|
<p>{t('_template.description')}</p>
|
||||||
<textarea className="input-field" value={draft.template ?? defaultTemplate} placeholder={defaultTemplate} style={{height: 228}} onChange={(e) => {
|
<textarea className="input-field" value={draft.template ?? defaultTemplate} placeholder={defaultTemplate} style={{height: 228}} onChange={(e) => {
|
||||||
dispatchDraft({ template: e.target.value });
|
dispatchDraft({ template: e.target.value });
|
||||||
}} />
|
}} />
|
||||||
<small className="text-dimmed">
|
<small className="text-dimmed">{t('_template.description2')}</small>
|
||||||
ハッシュタグ #misshaialert は、テンプレートに関わらず自動付与されます。
|
|
||||||
</small>
|
|
||||||
<details>
|
<details>
|
||||||
<summary>ヘルプ</summary>
|
<summary>{t('help')}</summary>
|
||||||
<ul className="fade">
|
<ul className="fade">
|
||||||
<li><code>{'{'}notesCount{'}'}</code>といった形式のテキストは変数として扱われ、これを含めると投稿時に自動的に値が埋め込まれます。</li>
|
<li><code>{'{'}notesCount{'}'}</code>といった形式のテキストは変数として扱われ、これを含めると投稿時に自動的に値が埋め込まれます。</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -236,26 +221,20 @@ export const SettingPage: React.VFC = () => {
|
||||||
<button className="btn danger" onClick={() => dispatchDraft({ template: null })}>初期値に戻す</button>
|
<button className="btn danger" onClick={() => dispatchDraft({ template: null })}>初期値に戻す</button>
|
||||||
<button className="btn primary" onClick={() => {
|
<button className="btn primary" onClick={() => {
|
||||||
updateSettingWithDialog({ template: draft.template === '' ? null : draft.template });
|
updateSettingWithDialog({ template: draft.template === '' ? null : draft.template });
|
||||||
}}>保存</button>
|
}}>{t('save')}</button>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<Card bodyClassName="vstack">
|
<Card bodyClassName="vstack">
|
||||||
<button className="btn block" onClick={onClickSendAlert}>アラートをテスト送信する</button>
|
<button className="btn block" onClick={onClickSendAlert}>{t('sendAlert')}</button>
|
||||||
<p className="text-dimmed">
|
<p className="text-dimmed">{t('sendAlertDescription')}</p>
|
||||||
現在の設定を用いて、アラート送信をテストします。
|
|
||||||
</p>
|
|
||||||
</Card>
|
</Card>
|
||||||
<Card bodyClassName="vstack">
|
<Card bodyClassName="vstack">
|
||||||
<button className="btn block" onClick={onClickLogout}>ログアウトする</button>
|
<button className="btn block" onClick={onClickLogout}>{t('logout')}</button>
|
||||||
<p className="text-dimmed">
|
<p className="text-dimmed">{t('logoutDescription')}</p>
|
||||||
ログアウトしても、アラートは送信されます。
|
|
||||||
</p>
|
|
||||||
</Card>
|
</Card>
|
||||||
<Card bodyClassName="vstack">
|
<Card bodyClassName="vstack">
|
||||||
<button className="btn danger block" onClick={onClickDeleteAccount}>アカウント連携を解除する</button>
|
<button className="btn danger block" onClick={onClickDeleteAccount}>{t('deleteAccount')}</button>
|
||||||
<p className="text-dimmed">
|
<p className="text-dimmed">{t('deleteAccountDescription')}</p>
|
||||||
Misskeyとの連携設定を含むみす廃アラートのアカウントを削除します。
|
|
||||||
</p>
|
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,5 +1,99 @@
|
||||||
{
|
{
|
||||||
translation: {
|
translation: {
|
||||||
title: "Misskey Alert",
|
title: "Misskey Tools",
|
||||||
}
|
description1: "Misskeyは楽しいものです。気がついたら1日中入り浸っていることも多いでしょう。",
|
||||||
|
description2: "さあ、今すぐMisskey Toolsをインストールして、あなたの活動を把握しよう。",
|
||||||
|
notes: "Notes",
|
||||||
|
following: "Following",
|
||||||
|
followers: "Followers",
|
||||||
|
welcomeBack: "Welcome back, {{acct}}.",
|
||||||
|
alertMode: "How to Send Alerts",
|
||||||
|
visibility: "Visibility",
|
||||||
|
appearance: "Appearance",
|
||||||
|
template: "Template",
|
||||||
|
sendAlert: "Test your alert",
|
||||||
|
sendAlertDescription: "Send your alert with current settings to test.",
|
||||||
|
logout: "Log Out",
|
||||||
|
logoutDescription: "If you log out, alerts will be sent.",
|
||||||
|
deleteAccount: "Deactivate account integration",
|
||||||
|
deleteAccountDescription: "Delete your Misskey Tools account. This will deactivate integration with Misskey.",
|
||||||
|
instanceUrl: "Instance URL",
|
||||||
|
login: "Login",
|
||||||
|
localOnly: "Local Only",
|
||||||
|
remoteFollowersOnly: "Remote Followers and Local Only",
|
||||||
|
help: "Help",
|
||||||
|
save: "Save",
|
||||||
|
theme: "Theme",
|
||||||
|
language: "Language",
|
||||||
|
fetching: "Fetching...",
|
||||||
|
failedToFetch: "Failed to fetch",
|
||||||
|
registeredUsersCount: "Users",
|
||||||
|
isCalculating: "It is being calculated now. Please check back later!",
|
||||||
|
ok: "OK",
|
||||||
|
yes: "Yes",
|
||||||
|
no: "No",
|
||||||
|
termsOfService: "Terms of Service",
|
||||||
|
name: "Name",
|
||||||
|
_welcomeMessage: {
|
||||||
|
pattern1: "Overnoting Misskey?",
|
||||||
|
pattern2: "Overusing Misskey?",
|
||||||
|
pattern3: "How many notes did you write today?",
|
||||||
|
pattern4: "Do you really think you're lite-misskist?",
|
||||||
|
pattern5: "Misskey is my breathing!",
|
||||||
|
pattern6: "I'm Misskey freak!",
|
||||||
|
},
|
||||||
|
_sendAlertDialog: {
|
||||||
|
title: "Are you sure you want to send the alert for testing?",
|
||||||
|
message: "Send an alert with the current settings. Be sure to check whether you saved the settings before executing.",
|
||||||
|
success: "Alert sent.",
|
||||||
|
error: "Failed to send alert.",
|
||||||
|
},
|
||||||
|
_nav: {
|
||||||
|
data: "Data",
|
||||||
|
ranking: "Ranking",
|
||||||
|
settings: "Settings",
|
||||||
|
},
|
||||||
|
_missHai: {
|
||||||
|
ranking: "Miss-hai Ranking",
|
||||||
|
rankingDescription1: "ユーザーの「みす廃レート」を算出し、高い順にランキング表示しています。みす廃レートは、次のような条件で算出されます。",
|
||||||
|
rankingFormula: "(ノート数) / (アカウント登録からの経過日数)",
|
||||||
|
rankingDescription2: "廃人を極めるか、ノート数を控えるか、全てあなた次第!",
|
||||||
|
showAll: "Show All",
|
||||||
|
data: "Miss-hai Data",
|
||||||
|
dataBody: "",
|
||||||
|
dataScore: "Score",
|
||||||
|
dataDelta: "Difference",
|
||||||
|
rating: "Rating",
|
||||||
|
order: "Order",
|
||||||
|
},
|
||||||
|
_developerInfo: {
|
||||||
|
title: "Developer",
|
||||||
|
description: "If you want supports, send messages to the below accounts.",
|
||||||
|
},
|
||||||
|
_timeline: {
|
||||||
|
title: "Timeline",
|
||||||
|
description: "Shows latest notes including {{hashtag}} tags.",
|
||||||
|
},
|
||||||
|
_alertMode: {
|
||||||
|
note: "Automatic Note",
|
||||||
|
notification: "Notify to your account (Default)",
|
||||||
|
nothing: "Do Nothing",
|
||||||
|
notificationWarning: "'Notify to your account' option will not be work on legacy versions of Misskey.",
|
||||||
|
},
|
||||||
|
_visibility: {
|
||||||
|
public: "Public",
|
||||||
|
home: "Home",
|
||||||
|
followers: "Followers",
|
||||||
|
users: "Logged-in Users",
|
||||||
|
},
|
||||||
|
_themes: {
|
||||||
|
light: "Light",
|
||||||
|
dark: "Dark",
|
||||||
|
system: "Follows System Preferences",
|
||||||
|
},
|
||||||
|
_template: {
|
||||||
|
description: "アラートの自動投稿をカスタマイズできます。",
|
||||||
|
description2: "ハッシュタグ #misshaialert は、テンプレートに関わらず自動付与されます。",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,99 @@
|
||||||
{
|
{
|
||||||
translation: {
|
translation: {
|
||||||
title: "みす廃アラート",
|
title: "Misskey Tools",
|
||||||
}
|
description1: "Misskeyは楽しいものです。気がついたら1日中入り浸っていることも多いでしょう。",
|
||||||
|
description2: "さあ、今すぐMisskey Toolsをインストールして、あなたの活動を把握しよう。",
|
||||||
|
notes: "ノート",
|
||||||
|
following: "フォロー",
|
||||||
|
followers: "フォロワー",
|
||||||
|
welcomeBack: "おかえりなさい、{{acct}}さん。",
|
||||||
|
alertMode: "アラート送信方法",
|
||||||
|
visibility: "公開範囲",
|
||||||
|
appearance: "表示設定",
|
||||||
|
template: "テンプレート",
|
||||||
|
sendAlert: "アラートをテスト送信する",
|
||||||
|
sendAlertDescription: "現在の設定を用いて、アラート送信をテストします。",
|
||||||
|
logout: "ログアウトする",
|
||||||
|
logoutDescription: "ログアウトしても、アラートは送信されます。",
|
||||||
|
deleteAccount: "アカウント連携を解除する",
|
||||||
|
deleteAccountDescription: "Misskey Toolsのアカウントを削除します。これにより、Misskeyとの連携設定も解除されます。",
|
||||||
|
instanceUrl: "インスタンスURL",
|
||||||
|
login: "ログイン",
|
||||||
|
localOnly: "ローカルのみ",
|
||||||
|
remoteFollowersOnly: "リモートフォロワーとローカル",
|
||||||
|
help: "ヘルプ",
|
||||||
|
save: "保存",
|
||||||
|
theme: "テーマ",
|
||||||
|
language: "言語",
|
||||||
|
fetching: "取得中……",
|
||||||
|
failedToFetch: "取得に失敗しました",
|
||||||
|
registeredUsersCount: "登録者数",
|
||||||
|
isCalculating: "現在算出中です。後ほどご確認ください!",
|
||||||
|
ok: "OK",
|
||||||
|
yes: "はい",
|
||||||
|
no: "いいえ",
|
||||||
|
termsOfService: "利用規約",
|
||||||
|
name: "名前",
|
||||||
|
_welcomeMessage: {
|
||||||
|
'pattern1': 'ついついノートしすぎていませんか?',
|
||||||
|
'pattern2': 'Misskey, しすぎていませんか?',
|
||||||
|
'pattern3': '今日、何ノート書いた?',
|
||||||
|
'pattern4': '10000 ノートは初心者、そう思っていませんか?',
|
||||||
|
'pattern5': '息するように Misskey、そんなあなたへ。',
|
||||||
|
'pattern6': 'あなたは真の Misskey 廃人ですか?',
|
||||||
|
},
|
||||||
|
_sendAlertDialog: {
|
||||||
|
title: "アラートをテスト送信しますか?",
|
||||||
|
message: "現在の設定でアラートを送信します。設定が保存済みであるかどうか、実行前に必ずご確認ください。",
|
||||||
|
success: "送信しました。",
|
||||||
|
error: "送信に失敗しました。",
|
||||||
|
},
|
||||||
|
_nav: {
|
||||||
|
data: "データ",
|
||||||
|
ranking: "ランキング",
|
||||||
|
settings: "設定",
|
||||||
|
},
|
||||||
|
_missHai: {
|
||||||
|
ranking: "ミス廃ランキング",
|
||||||
|
rankingDescription1: "ユーザーの「みす廃レート」を算出し、高い順にランキング表示しています。みす廃レートは、次のような条件で算出されます。",
|
||||||
|
rankingFormula: "(ノート数) / (アカウント登録からの経過日数)",
|
||||||
|
rankingDescription2: "廃人を極めるか、ノート数を控えるか、全てあなた次第!",
|
||||||
|
showAll: "全員分見る",
|
||||||
|
data: "みす廃データ",
|
||||||
|
dataBody: "内容",
|
||||||
|
dataScore: "スコア",
|
||||||
|
dataDelta: "前日比",
|
||||||
|
rating: "レート",
|
||||||
|
order: "順位",
|
||||||
|
},
|
||||||
|
_developerInfo: {
|
||||||
|
title: "開発者",
|
||||||
|
description: "何か困ったことがあったら、以下のアカウントにメッセージを送ってください。",
|
||||||
|
},
|
||||||
|
_timeline: {
|
||||||
|
title: "タイムライン",
|
||||||
|
description: "{{hashtag}} タグを含む最新ノートを表示します。",
|
||||||
|
},
|
||||||
|
_alertMode: {
|
||||||
|
note: "自動的にノートを投稿",
|
||||||
|
notification: "Misskeyに通知(標準)",
|
||||||
|
nothing: "通知しない",
|
||||||
|
notificationWarning: "「Misskey に通知」オプションは古いMisskeyでは動作しません。",
|
||||||
|
},
|
||||||
|
_visibility: {
|
||||||
|
public: "パブリック",
|
||||||
|
home: "ホーム",
|
||||||
|
followers: "フォロワー",
|
||||||
|
users: "ログインユーザー",
|
||||||
|
},
|
||||||
|
_themes: {
|
||||||
|
light: "ライトテーマ",
|
||||||
|
dark: "ダークテーマ",
|
||||||
|
system: "システム設定に準じる",
|
||||||
|
},
|
||||||
|
_template: {
|
||||||
|
description: "アラートの自動投稿をカスタマイズできます。",
|
||||||
|
description2: "ハッシュタグ #misshaialert は、テンプレートに関わらず自動付与されます。",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,13 @@
|
||||||
export type Theme = ActualTheme | 'system';
|
export const actualThemes = [
|
||||||
|
'light',
|
||||||
|
'dark',
|
||||||
|
] as const;
|
||||||
|
|
||||||
export type ActualTheme = 'light' | 'dark';
|
export const themes = [
|
||||||
|
...actualThemes,
|
||||||
|
'system',
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export type Theme = typeof themes[number];
|
||||||
|
|
||||||
|
export type ActualTheme = typeof actualThemes[number];
|
||||||
|
|
|
@ -5,15 +5,17 @@ import { SessionDataPage } from '../components/SessionDataPage';
|
||||||
import { Ranking } from '../components/Ranking';
|
import { Ranking } from '../components/Ranking';
|
||||||
import { Tab, TabItem } from '../components/Tab';
|
import { Tab, TabItem } from '../components/Tab';
|
||||||
import { SettingPage } from '../components/SettingPage';
|
import { SettingPage } from '../components/SettingPage';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export const IndexSessionPage: React.VFC = () => {
|
export const IndexSessionPage: React.VFC = () => {
|
||||||
const [selectedTab, setSelectedTab] = useState<number>(0);
|
const [selectedTab, setSelectedTab] = useState<number>(0);
|
||||||
|
const {t, i18n} = useTranslation();
|
||||||
|
|
||||||
const items = useMemo<TabItem[]>(() => ([
|
const items = useMemo<TabItem[]>(() => ([
|
||||||
{ label: 'データ' },
|
{ label: t('_nav.data') },
|
||||||
{ label: 'ランキング' },
|
{ label: t('_nav.ranking') },
|
||||||
{ label: '設定' },
|
{ label: t('_nav.settings') },
|
||||||
]), []);
|
]), [i18n.language]);
|
||||||
|
|
||||||
const component = useMemo(() => {
|
const component = useMemo(() => {
|
||||||
switch (selectedTab) {
|
switch (selectedTab) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { Ranking } from '../components/Ranking';
|
import { Ranking } from '../components/Ranking';
|
||||||
import { LoginForm } from '../components/LoginForm';
|
import { LoginForm } from '../components/LoginForm';
|
||||||
|
@ -8,20 +9,22 @@ import { HashtagTimeline } from '../components/HashtagTimeline';
|
||||||
import { Header } from '../components/Header';
|
import { Header } from '../components/Header';
|
||||||
|
|
||||||
export const IndexWelcomePage: React.VFC = () => {
|
export const IndexWelcomePage: React.VFC = () => {
|
||||||
|
const {t} = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header>
|
<Header>
|
||||||
<article className="mt-4">
|
<article className="mt-4">
|
||||||
<p>Misskeyは楽しいものです。気がついたら1日中入り浸っていることも多いでしょう。</p>
|
<p>{t('description1')}</p>
|
||||||
<p>さあ、今すぐみす廃アラートをインストールして、あなたの活動を把握しよう。</p>
|
<p>{t('description2')}</p>
|
||||||
</article>
|
</article>
|
||||||
<LoginForm />
|
<LoginForm />
|
||||||
</Header>
|
</Header>
|
||||||
<article className="xarticle card ghost">
|
<article className="xarticle card ghost">
|
||||||
<div className="body">
|
<div className="body">
|
||||||
<h1 className="mb-1">みす廃ランキング</h1>
|
<h1 className="mb-1">{t('_missHai.ranking')}</h1>
|
||||||
<Ranking limit={10} />
|
<Ranking limit={10} />
|
||||||
<Link to="/ranking">全員分見る</Link>
|
<Link to="/ranking">{t('_missHai.showAll')}</Link>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
<article className="xarticle mt-4 row">
|
<article className="xarticle mt-4 row">
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Ranking } from '../components/Ranking';
|
import { Ranking } from '../components/Ranking';
|
||||||
|
|
||||||
|
|
||||||
export const RankingPage: React.VFC = () => {
|
export const RankingPage: React.VFC = () => {
|
||||||
|
const {t} = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article className="xarticle">
|
<article className="xarticle">
|
||||||
<h2>みす廃ランキング</h2>
|
<h2>{t('_missHai.ranking')}</h2>
|
||||||
<section>
|
<section>
|
||||||
<p>
|
<p>{t('_missHai.rankingDescription1')}</p>
|
||||||
ユーザーの「みす廃レート」を算出し、高い順にランキング表示しています。
|
<p><strong>{t('_missHai.rankingFormula')}</strong></p>
|
||||||
みす廃レートは、次のような条件で算出されます。
|
<p>{t('_missHai.rankingDescription2')}</p>
|
||||||
</p>
|
|
||||||
<p><strong>(ノート数) / (アカウント登録からの経過日数)</strong></p>
|
|
||||||
<p>廃人を極めるか、ノート数を控えるか、全てあなた次第!</p>
|
|
||||||
</section>
|
</section>
|
||||||
<section className="pt-2">
|
<section className="pt-2">
|
||||||
<Ranking />
|
<Ranking />
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
export const TermPage: React.VFC = () => {
|
export const TermPage: React.VFC = () => {
|
||||||
|
// TODO: 外部サイトに誘導する
|
||||||
return (
|
return (
|
||||||
<article className="xarticle">
|
<article className="xarticle">
|
||||||
<h2>利用規約</h2>
|
<h2>利用規約</h2>
|
||||||
|
|
|
@ -71,8 +71,10 @@ small {
|
||||||
z-index: 40000;
|
z-index: 40000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .card.dialog {
|
.card.dialog {
|
||||||
|
.dark & {
|
||||||
background: var(--tone-2);
|
background: var(--tone-2);
|
||||||
|
}
|
||||||
min-width: min(100vw, 320px);
|
min-width: min(100vw, 320px);
|
||||||
max-width: min(100vw, 600px);
|
max-width: min(100vw, 600px);
|
||||||
}
|
}
|
10
yarn.lock
10
yarn.lock
|
@ -411,6 +411,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-1.8.1.tgz#e81ad28a60bee0328c6d2384e029aec626f1ae67"
|
resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-1.8.1.tgz#e81ad28a60bee0328c6d2384e029aec626f1ae67"
|
||||||
integrity sha512-e+2rjEwK6KDaNOm5Aa9wNGgyS9oSZU/4pfSMMPYNOfjvFI0WVXm29+ITRFr6aKDvvKo7uU1jV68MW4ScsfDi7Q==
|
integrity sha512-e+2rjEwK6KDaNOm5Aa9wNGgyS9oSZU/4pfSMMPYNOfjvFI0WVXm29+ITRFr6aKDvvKo7uU1jV68MW4ScsfDi7Q==
|
||||||
|
|
||||||
|
"@types/insert-text-at-cursor@^0.3.0":
|
||||||
|
version "0.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/insert-text-at-cursor/-/insert-text-at-cursor-0.3.0.tgz#c7fafad758e26cd7f6bd82c46bf3601fec7afc3e"
|
||||||
|
integrity sha512-58a+1l6hz5DQ25NjODvZEYmJUWA1ALbUxcf/GHjMfh92r41lZkI6buNN0NwxIvJQDkTFNqL73G2Fduu5ctHVhA==
|
||||||
|
|
||||||
"@types/json-schema@*", "@types/json-schema@^7.0.7", "@types/json-schema@^7.0.8":
|
"@types/json-schema@*", "@types/json-schema@^7.0.7", "@types/json-schema@^7.0.8":
|
||||||
version "7.0.9"
|
version "7.0.9"
|
||||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d"
|
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d"
|
||||||
|
@ -2703,6 +2708,11 @@ ini@^1.3.4, ini@~1.3.0:
|
||||||
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
|
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
|
||||||
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
|
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
|
||||||
|
|
||||||
|
insert-text-at-cursor@^0.3.0:
|
||||||
|
version "0.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/insert-text-at-cursor/-/insert-text-at-cursor-0.3.0.tgz#1819607680ec1570618347c4cd475e791faa25da"
|
||||||
|
integrity sha512-/nPtyeX9xPUvxZf+r0518B7uqNKlP+LqNJqSiXFEaa2T71rWIwTVXGH7hB9xO/EVdwa5/pWlFCPwShOW81XIxQ==
|
||||||
|
|
||||||
internal-slot@^1.0.3:
|
internal-slot@^1.0.3:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c"
|
resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue