0
0
Fork 0
This commit is contained in:
Xeltica 2021-09-14 14:28:40 +09:00
parent d06f2384dc
commit 230e952c84
19 changed files with 312 additions and 115 deletions

View file

@ -25,6 +25,7 @@
"dependencies": {
"@babel/preset-react": "^7.14.5",
"@reduxjs/toolkit": "^1.6.1",
"@types/insert-text-at-cursor": "^0.3.0",
"@types/koa-bodyparser": "^4.3.0",
"@types/koa-multer": "^1.0.0",
"@types/koa-send": "^4.1.3",
@ -45,6 +46,7 @@
"fibers": "^5.0.0",
"i18next": "^20.6.1",
"i18next-browser-languagedetector": "^6.1.2",
"insert-text-at-cursor": "^0.3.0",
"json5-loader": "^4.0.1",
"koa": "^2.13.0",
"koa-bodyparser": "^4.3.0",

View file

@ -1,6 +1,7 @@
import React, { useState, useEffect, useCallback } from 'react';
import { BrowserRouter, Link, Route, Switch, useLocation } from 'react-router-dom';
import { Provider } from 'react-redux';
import { useTranslation } from 'react-i18next';
import { IndexPage } from './pages';
import { RankingPage } from './pages/ranking';
@ -46,6 +47,8 @@ const AppInner : React.VFC = () => {
};
}, [osTheme, setOsTheme]);
const {t} = useTranslation();
return (
<>
<div className="container">
@ -57,7 +60,7 @@ const AppInner : React.VFC = () => {
</Switch>
<footer className="text-center pa-5">
<p>(C)2020-2021 Xeltica</p>
<p><Link to="/term"></Link></p>
<p><Link to="/term">{t('termsOfService')}</Link></p>
</footer>
<ModalComponent />
</div>

View file

@ -73,7 +73,7 @@ export const ModalComponent: React.VFC = () => {
if (!shown || !modal) return null;
return (
<div className="modal" onClick={() => dispatch(hideModal())}>
<div className="modal fade" onClick={() => dispatch(hideModal())}>
<div className="fade up" onClick={(e) => e.stopPropagation()}>
{ ModalInner(modal) }
</div>

View file

@ -1,10 +1,12 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
export const DeveloperInfo: React.VFC = () => {
const {t} = useTranslation();
return (
<>
<h1></h1>
<p></p>
<h1>{t('_developerInfo.title')}</h1>
<p>{t('_developerInfo.description')}</p>
<ul>
<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>

View file

@ -1,14 +1,16 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
export type HashtagTimelineProps = {
hashtag: string;
};
export const HashtagTimeline: React.VFC<HashtagTimelineProps> = ({hashtag}) => {
const {t} = useTranslation();
return (
<>
<h1></h1>
<p>#{hashtag} </p>
<h1>{t('_timeline.title')}</h1>
<p>{t('_timeline.description', { hashtag })}</p>
<p>WIP</p>
</>
);

View file

@ -8,19 +8,17 @@ export type HeaderProps = {
hasTopLink?: boolean;
};
const messageNumber = Math.floor(Math.random() * 6) + 1;
export const Header: React.FC<HeaderProps> = ({hasTopLink, children}) => {
const { t } = useTranslation();
const message = React.useMemo(
() => welcomeMessage[Math.floor(Math.random() * welcomeMessage.length)] , []);
return (
<header className={'xarticle card shadow-4 mt-5 mb-3'}>
<header className={'xarticle card mt-5 mb-3'}>
<div className="body">
<h1 className="text-primary mb-0" style={{ fontSize: '2rem' }}>
{hasTopLink ? <Link to="/">{t('title')}</Link> : t('title')}
</h1>
<h2 className="text-dimmed ml-1">{message}</h2>
<h2 className="text-dimmed ml-1">{t(`_welcomeMessage.pattern${messageNumber}`)}</h2>
{children}
</div>
</header>

View file

@ -1,18 +1,19 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
export const LoginForm: React.VFC = () => {
const [host, setHost] = useState('');
const {t} = useTranslation();
return (
<nav>
<div>
<strong>URL</strong>
<strong>{t('instanceUrl')}</strong>
</div>
<div className="hgroup">
<input
className="input-field"
type="text"
placeholder="例: misskey.io"
value={host}
onChange={(e) => setHost(e.target.value)}
required
@ -23,7 +24,7 @@ export const LoginForm: React.VFC = () => {
disabled={!host}
onClick={() => location.href = `//${location.host}/login?host=${encodeURIComponent(host)}`}
>
{t('login')}
</button>
</div>
</nav>

View file

@ -1,4 +1,5 @@
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
interface RankingResponse {
isCalculating: boolean;
@ -21,6 +22,7 @@ export const Ranking: React.VFC<RankingProps> = ({limit}) => {
const [response, setResponse] = useState<RankingResponse | null>(null);
const [isFetching, setIsFetching] = useState(true);
const [isError, setIsError] = useState(false);
const {t} = useTranslation();
// APIコール
useEffect(() => {
@ -39,27 +41,27 @@ export const Ranking: React.VFC<RankingProps> = ({limit}) => {
return (
isFetching ? (
<p className="text-dimmed"></p>
<p className="text-dimmed">{t('fetching')}</p>
) : isError ? (
<div className="alert bg-danger"></div>
<div className="alert bg-danger">{t('failedToFetch')}</div>
) : response ? (
<>
<aside>{response?.userCount}</aside>
<aside>{t('registeredUsersCount')}: {response?.userCount}</aside>
{response.isCalculating ? (
<p></p>
<p>{t('isCalculating')}</p>
) : (
<table className="table shadow-2 mt-1 fluid">
<table className="table mt-1 fluid">
<thead>
<tr>
<th></th>
<th></th>
<th></th>
<th>{t('_missHai.order')}</th>
<th>{t('name')}</th>
<th>{t('_missHai.rating')}</th>
</tr>
</thead>
<tbody>
{response.ranking.map((r, i) => (
<tr key={i}>
<td>{i + 1}</td>
<td>{i + 1}</td>
<td>
{r.username}@{r.host}
</td>

View file

@ -1,4 +1,5 @@
import React, { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { LOCALSTORAGE_KEY_TOKEN } from '../const';
import { useGetScoreQuery, useGetSessionQuery } from '../services/session';
import { Skeleton } from './Skeleton';
@ -6,6 +7,7 @@ import { Skeleton } from './Skeleton';
export const SessionDataPage: React.VFC = () => {
const session = useGetSessionQuery(undefined);
const score = useGetScoreQuery(undefined);
const {t} = useTranslation();
/**
* Session APIのエラーハンドリング
@ -30,20 +32,10 @@ export const SessionDataPage: React.VFC = () => {
<div className="fade">
{session.data && (
<section>
<p>
<a
href={`https://${session.data.host}/@${session.data.username}`}
target="_blank"
rel="noreferrer noopener"
>
@{session.data.username}@{session.data.host}
</a>
</p>
<p>{t('welcomeBack', {acct: `@${session.data.username}@${session.data.host}`})}</p>
<p>
<strong>
:
{t('_missHai.rating')}:
</strong>
{session.data.rating}
</p>
@ -51,28 +43,28 @@ export const SessionDataPage: React.VFC = () => {
)}
{score.data && (
<section>
<h2></h2>
<table className="table fluid shadow-2" style={{border: 'none'}}>
<h2>{t('_missHai.data')}</h2>
<table className="table fluid">
<thead>
<tr>
<th></th>
<th></th>
<th></th>
<th></th>
<th>{t('_missHai.dataScore')}</th>
<th>{t('_missHai.dataDelta')}</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td>{t('notes')}</td>
<td>{score.data.notesCount}</td>
<td>{score.data.notesDelta}</td>
</tr>
<tr>
<td></td>
<td>{t('following')}</td>
<td>{score.data.followingCount}</td>
<td>{score.data.followingDelta}</td>
</tr>
<tr>
<td></td>
<td>{t('followers')}</td>
<td>{score.data.followersCount}</td>
<td>{score.data.followersDelta}</td>
</tr>

View file

@ -5,12 +5,13 @@ import { Visibility } from '../../common/types/visibility';
import { useGetSessionQuery } from '../services/session';
import { defaultTemplate } from '../../common/default-template';
import { Card } from './Card';
import { Theme } from '../misc/theme';
import { API_ENDPOINT, LOCALSTORAGE_KEY_LANG, LOCALSTORAGE_KEY_TOKEN } from '../const';
import { Theme, themes } from '../misc/theme';
import { API_ENDPOINT, LOCALSTORAGE_KEY_TOKEN } from '../const';
import { useDispatch } from 'react-redux';
import { changeLang, changeTheme, showModal } from '../store/slices/screen';
import { useSelector } from '../store';
import { languageName } from '../langs';
import { useTranslation } from 'react-i18next';
type SettingDraftType = Partial<Pick<IUser,
| 'alertMode'
@ -27,6 +28,7 @@ export const SettingPage: React.VFC = () => {
const dispatch = useDispatch();
const data = session.data;
const {t} = useTranslation();
const [draft, dispatchDraft] = useReducer<DraftReducer>((state, action) => {
return { ...state, ...action };
@ -38,23 +40,8 @@ export const SettingPage: React.VFC = () => {
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 currentLang = useSelector(state => state.screen.lang);
const currentLang = useSelector(state => state.screen.language);
const availableVisibilities: Visibility[] = [
'public',
@ -150,7 +137,7 @@ export const SettingPage: React.VFC = () => {
) : (
<div className="vstack fade">
<Card bodyClassName="vstack">
<h1></h1>
<h1>{t('alertMode')}</h1>
<div>
{
alertModes.map((mode) => (
@ -158,27 +145,27 @@ export const SettingPage: React.VFC = () => {
<input type="radio" checked={mode === draft.alertMode} onChange={() => {
updateSetting({ alertMode: mode });
}} />
<span>{mode}</span>
<span>{t(`_alertMode.${mode}`)}</span>
</label>
))
}
{draft.alertMode === 'notification' && (
<div className="alert bg-danger mt-2">
<i className="icon bi bi-exclamation-circle"></i>
Misskey Misskeyでは動作しません
{t('_alertMode.notificationWarning')}
</div>
)}
</div>
{ draft.alertMode === 'note' && (
<div>
<label htmlFor="visibility" className="input-field"></label>
<label htmlFor="visibility" className="input-field">{t('visibility')}</label>
{
availableVisibilities.map((visibility) => (
<label key={visibility} className="input-check">
<input type="radio" checked={visibility === draft.visibility} onChange={() => {
updateSetting({ visibility });
}} />
<span>{visibility}</span>
<span>{t(`_visibility.${visibility}`)}</span>
</label>
))
}
@ -186,26 +173,26 @@ export const SettingPage: React.VFC = () => {
<input type="checkbox" checked={draft.localOnly} onChange={(e) => {
updateSetting({ localOnly: e.target.checked });
}} />
<span></span>
<span>{t('localOnly')}</span>
</label>
</div>
)}
</Card>
<Card bodyClassName="vstack">
<h1></h1>
<h2></h2>
<h1>{t('appearance')}</h1>
<h2>{t('theme')}</h2>
<div>
{
themes.map(({ theme, name }) => (
themes.map(theme => (
<label key={theme} className="input-check">
<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>
))
}
</div>
<h2></h2>
<h2>{t('language')}</h2>
<select name="currentLang" className="input-field" value={currentLang} onChange={(e) => {
dispatch(changeLang(e.target.value));
}}>
@ -218,16 +205,14 @@ export const SettingPage: React.VFC = () => {
</Card>
<Card bodyClassName="vstack">
<h1></h1>
<p>稿</p>
<h1>{t('template')}</h1>
<p>{t('_template.description')}</p>
<textarea className="input-field" value={draft.template ?? defaultTemplate} placeholder={defaultTemplate} style={{height: 228}} onChange={(e) => {
dispatchDraft({ template: e.target.value });
}} />
<small className="text-dimmed">
#misshaialert
</small>
<small className="text-dimmed">{t('_template.description2')}</small>
<details>
<summary></summary>
<summary>{t('help')}</summary>
<ul className="fade">
<li><code>{'{'}notesCount{'}'}</code>稿</li>
</ul>
@ -236,26 +221,20 @@ export const SettingPage: React.VFC = () => {
<button className="btn danger" onClick={() => dispatchDraft({ template: null })}></button>
<button className="btn primary" onClick={() => {
updateSettingWithDialog({ template: draft.template === '' ? null : draft.template });
}}></button>
}}>{t('save')}</button>
</div>
</Card>
<Card bodyClassName="vstack">
<button className="btn block" onClick={onClickSendAlert}></button>
<p className="text-dimmed">
</p>
<button className="btn block" onClick={onClickSendAlert}>{t('sendAlert')}</button>
<p className="text-dimmed">{t('sendAlertDescription')}</p>
</Card>
<Card bodyClassName="vstack">
<button className="btn block" onClick={onClickLogout}></button>
<p className="text-dimmed">
</p>
<button className="btn block" onClick={onClickLogout}>{t('logout')}</button>
<p className="text-dimmed">{t('logoutDescription')}</p>
</Card>
<Card bodyClassName="vstack">
<button className="btn danger block" onClick={onClickDeleteAccount}></button>
<p className="text-dimmed">
Misskeyとの連携設定を含むみす廃アラートのアカウントを削除します
</p>
<button className="btn danger block" onClick={onClickDeleteAccount}>{t('deleteAccount')}</button>
<p className="text-dimmed">{t('deleteAccountDescription')}</p>
</Card>
</div>
);

View file

@ -1,5 +1,99 @@
{
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 は、テンプレートに関わらず自動付与されます。",
},
},
}

View file

@ -1,5 +1,99 @@
{
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 は、テンプレートに関わらず自動付与されます。",
},
},
}

View file

@ -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];

View file

@ -5,15 +5,17 @@ import { SessionDataPage } from '../components/SessionDataPage';
import { Ranking } from '../components/Ranking';
import { Tab, TabItem } from '../components/Tab';
import { SettingPage } from '../components/SettingPage';
import { useTranslation } from 'react-i18next';
export const IndexSessionPage: React.VFC = () => {
const [selectedTab, setSelectedTab] = useState<number>(0);
const {t, i18n} = useTranslation();
const items = useMemo<TabItem[]>(() => ([
{ label: 'データ' },
{ label: 'ランキング' },
{ label: '設定' },
]), []);
{ label: t('_nav.data') },
{ label: t('_nav.ranking') },
{ label: t('_nav.settings') },
]), [i18n.language]);
const component = useMemo(() => {
switch (selectedTab) {

View file

@ -1,5 +1,6 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Ranking } from '../components/Ranking';
import { LoginForm } from '../components/LoginForm';
@ -8,20 +9,22 @@ import { HashtagTimeline } from '../components/HashtagTimeline';
import { Header } from '../components/Header';
export const IndexWelcomePage: React.VFC = () => {
const {t} = useTranslation();
return (
<>
<Header>
<article className="mt-4">
<p>Misskeyは楽しいものです1</p>
<p></p>
<p>{t('description1')}</p>
<p>{t('description2')}</p>
</article>
<LoginForm />
</Header>
<article className="xarticle card ghost">
<div className="body">
<h1 className="mb-1"></h1>
<h1 className="mb-1">{t('_missHai.ranking')}</h1>
<Ranking limit={10} />
<Link to="/ranking"></Link>
<Link to="/ranking">{t('_missHai.showAll')}</Link>
</div>
</article>
<article className="xarticle mt-4 row">

View file

@ -1,18 +1,18 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Ranking } from '../components/Ranking';
export const RankingPage: React.VFC = () => {
const {t} = useTranslation();
return (
<article className="xarticle">
<h2></h2>
<h2>{t('_missHai.ranking')}</h2>
<section>
<p>
</p>
<p><strong>() / ()</strong></p>
<p></p>
<p>{t('_missHai.rankingDescription1')}</p>
<p><strong>{t('_missHai.rankingFormula')}</strong></p>
<p>{t('_missHai.rankingDescription2')}</p>
</section>
<section className="pt-2">
<Ranking />

View file

@ -1,6 +1,7 @@
import React from 'react';
export const TermPage: React.VFC = () => {
// TODO: 外部サイトに誘導する
return (
<article className="xarticle">
<h2></h2>

View file

@ -71,8 +71,10 @@ small {
z-index: 40000;
}
.dark .card.dialog {
background: var(--tone-2);
.card.dialog {
.dark & {
background: var(--tone-2);
}
min-width: min(100vw, 320px);
max-width: min(100vw, 600px);
}

View file

@ -411,6 +411,11 @@
resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-1.8.1.tgz#e81ad28a60bee0328c6d2384e029aec626f1ae67"
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":
version "7.0.9"
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"
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:
version "1.0.3"
resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c"