i18n: login page

This commit is contained in:
HFO4 2022-04-30 16:48:42 +08:00
parent 2d20892994
commit d6dc1f9719
14 changed files with 277 additions and 52 deletions

View File

@ -45,6 +45,9 @@
"html-webpack-plugin": "4.0.0-beta.5",
"http-proxy-middleware": "^0.20.0",
"husky": "^4.2.5",
"i18next": "^21.6.16",
"i18next-browser-languagedetector": "^6.1.4",
"i18next-http-backend": "^1.4.0",
"identity-obj-proxy": "3.0.0",
"invariant": "^2.2.4",
"is-wsl": "^1.1.0",
@ -76,6 +79,7 @@
"react-dom": "^16.12.0",
"react-highlight-words": "^0.18.0",
"react-hotkeys": "^2.0.0",
"react-i18next": "^11.16.7",
"react-lazy-load-image-component": "^1.3.2",
"react-load-script": "^0.0.6",
"react-monaco-editor": "^0.36.0",

View File

@ -0,0 +1,19 @@
{
"login": {
"email": "Email",
"password": "Password",
"captcha": "CAPTCHA",
"signIn": "Sign in",
"signUp": "Sign up",
"signUpAccount": "Sign up",
"useFIDO2": "Use Hardware Authenticator",
"usePassword": "Use Password",
"forgetPassword": "Forgot password?",
"2FA": "2FA Verification",
"input2FACode": "Please enter the six-digit 2FA verification code",
"browserNotSupport": "Not supported by current browser or environment",
"success": "Sign in successful",
"title": "Sign in {{title}}",
"continue": "Continue"
}
}

View File

@ -0,0 +1,18 @@
{
"pageNotFound": "Page not found",
"unknownError": "Unknown error",
"errLoadingSiteConfig": "Unable to load site configuration: ",
"newVersionRefresh": "A new version of the current page is available and ready to be refreshed.",
"errorDetails": "Error details",
"renderError": "There is an error in the page rendering, please try refreshing this page.",
"errors": {
"40020": "Wrong password or email address",
"40017": "This account has been blocked",
"40018": "This account is not activated",
"40019": "This feature is not enabled",
"40001": "Wrong format of input parameters ({{message}})",
"40021": "User not found",
"40022": "Verification code not correct",
"40023": "Login session not exist"
}
}

View File

@ -0,0 +1,19 @@
{
"login": {
"email": "电子邮箱",
"password": "密码",
"captcha": "验证码",
"signIn": "登录",
"signUp": "注册",
"signUpAccount": "注册账号",
"useFIDO2": "使用外部验证器登录",
"usePassword": "使用密码登录",
"forgetPassword": "忘记密码",
"2FA": "二步验证",
"input2FACode": "请输入六位二步验证代码",
"browserNotSupport": "当前浏览器或环境不支持",
"success": "登录成功",
"title": "登录 {{title}}",
"continue": "下一步"
}
}

View File

@ -0,0 +1,18 @@
{
"pageNotFound": "页面不存在",
"unknownError": "未知错误",
"errLoadingSiteConfig": "无法加载站点配置:",
"errorDetails": "错误详情",
"newVersionRefresh": "当前页面有新版本可用,准备刷新。",
"renderError": "页面渲染出现错误,请尝试刷新此页面。",
"errors": {
"40020": "用户邮箱或密码错误",
"40017": "该账号已被封禁",
"40018": "该账号未激活",
"40019": "此功能未启用",
"40001": "输入参数格式有误 ({{message}})",
"40021": "用户不存在",
"40022": "验证代码不正确",
"40023": "登录会话不存在"
}
}

View File

@ -33,6 +33,7 @@ import PageLoading from "./component/Placeholder/PageLoading";
import CodeViewer from "./component/Viewer/Code";
import MusicPlayer from "./component/FileManager/MusicPlayer";
import EpubViewer from "./component/Viewer/Epub";
import { useTranslation } from "react-i18next";
const PDFViewer = React.lazy(() =>
import(/* webpackChunkName: "pdf" */ "./component/Viewer/PDF")
@ -42,6 +43,7 @@ export default function App() {
const themeConfig = useSelector((state) => state.siteConfig.theme);
const isLogin = useSelector((state) => state.viewUpdate.isLogin);
const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)");
const { t } = useTranslation();
const theme = React.useMemo(() => {
themeConfig.palette.type = prefersDarkMode ? "dark" : "light";
@ -61,6 +63,13 @@ export default function App() {
: themeConfig.palette.primary.main,
},
},
overrides: {
MuiButton: {
root: {
textTransform: "none",
},
},
},
});
changeThemeColor(
themeConfig.palette.type === "dark"
@ -164,11 +173,19 @@ export default function App() {
<Tasks />
</AuthRoute>
<NoAuthRoute exact path={`${path}login`} isLogin={isLogin}>
<NoAuthRoute
exact
path={`${path}login`}
isLogin={isLogin}
>
<LoginForm />
</NoAuthRoute>
<NoAuthRoute exact path={`${path}signup`} isLogin={isLogin}>
<NoAuthRoute
exact
path={`${path}signup`}
isLogin={isLogin}
>
<Register />
</NoAuthRoute>
@ -215,7 +232,7 @@ export default function App() {
</Route>
<Route path="*">
<NotFound msg={"页面不存在"} />
<NotFound msg={t("pageNotFound", { ns: "common" })} />
</Route>
</Switch>
</main>

View File

@ -30,7 +30,6 @@ export const ICPFooter = () => {
}
return (
<div className={classes.icp}>
{`备案号: `}
<Link
href="https://beian.miit.gov.cn/"
rel="noopener noreferrer"

View File

@ -13,7 +13,7 @@ import {
Paper,
Typography,
} from "@material-ui/core";
import { useHistory } from "react-router-dom";
import { Link as RouterLink, useHistory } from "react-router-dom";
import API from "../../middleware/Api";
import Auth from "../../middleware/Auth";
import { bufferDecode, bufferEncode } from "../../utils/index";
@ -22,7 +22,12 @@ import VpnIcon from "@material-ui/icons/VpnKeyOutlined";
import { useLocation } from "react-router";
import { ICPFooter } from "../Common/ICPFooter";
import { useCaptcha } from "../../hooks/useCaptcha";
import { applyThemes, setSessionStatus, toggleSnackbar } from "../../redux/explorer";
import {
applyThemes,
setSessionStatus,
toggleSnackbar,
} from "../../redux/explorer";
import { useTranslation } from "react-i18next";
const useStyles = makeStyles((theme) => ({
layout: {
@ -87,6 +92,8 @@ function useQuery() {
}
function LoginForm() {
const { t } = useTranslation();
const [email, setEmail] = useState("");
const [pwd, setPwd] = useState("");
const [loading, setLoading] = useState(false);
@ -95,7 +102,9 @@ function LoginForm() {
const [faCode, setFACode] = useState("");
const loginCaptcha = useSelector((state) => state.siteConfig.loginCaptcha);
const registerEnabled = useSelector((state) => state.siteConfig.registerEnabled);
const registerEnabled = useSelector(
(state) => state.siteConfig.registerEnabled
);
const title = useSelector((state) => state.siteConfig.title);
const authn = useSelector((state) => state.siteConfig.authn);
@ -143,7 +152,7 @@ function LoginForm() {
SetSessionStatus(true);
history.push("/home");
ToggleSnackbar("top", "right", "登录成功", "success");
ToggleSnackbar("top", "right", t("login.success"), "success");
localStorage.removeItem("siteConfigCache");
};
@ -151,7 +160,12 @@ function LoginForm() {
const authnLogin = (e) => {
e.preventDefault();
if (!navigator.credentials) {
ToggleSnackbar("top", "right", "当前浏览器或环境不支持", "warning");
ToggleSnackbar(
"top",
"right",
t("login.browserNotSupport"),
"warning"
);
return;
}
@ -261,13 +275,13 @@ function LoginForm() {
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
登录 {title}
{t("login.title", { title })}
</Typography>
{!useAuthn && (
<form className={classes.form} onSubmit={login}>
<FormControl margin="normal" required fullWidth>
<InputLabel htmlFor="email">
电子邮箱
{t("login.email")}
</InputLabel>
<Input
id="email"
@ -283,7 +297,7 @@ function LoginForm() {
</FormControl>
<FormControl margin="normal" required fullWidth>
<InputLabel htmlFor="password">
密码
{t("login.password")}
</InputLabel>
<Input
name="password"
@ -307,7 +321,7 @@ function LoginForm() {
}
className={classes.submit}
>
登录
{t("login.signIn")}
</Button>
</form>
)}
@ -315,7 +329,7 @@ function LoginForm() {
<form className={classes.form}>
<FormControl margin="normal" required fullWidth>
<InputLabel htmlFor="email">
电子邮箱
{t("login.email")}
</InputLabel>
<Input
id="email"
@ -338,17 +352,23 @@ function LoginForm() {
onClick={authnLogin}
className={classes.submit}
>
下一步
{t("login.continue")}
</Button>
</form>
)}
<Divider />
<div className={classes.link}>
<div>
<Link href={"/forget"}>忘记密码</Link>
<Link component={RouterLink} to={"/forget"}>
{t("login.forgetPassword")}
</Link>
</div>
<div>
{ registerEnabled && <Link href={"/signup"}>注册账号</Link> }
{registerEnabled && (
<Link component={RouterLink} to={"/signup"}>
{t("login.signUpAccount")}
</Link>
)}
</div>
</div>
@ -368,7 +388,7 @@ function LoginForm() {
marginRight: 8,
}}
/>
使用外部验证器登录
{t("login.useFIDO2")}
</>
)}
{useAuthn && (
@ -378,7 +398,7 @@ function LoginForm() {
marginRight: 8,
}}
/>
使用密码登录
{t("login.usePassword")}
</>
)}
</Button>
@ -392,12 +412,12 @@ function LoginForm() {
<VpnIcon />
</Avatar>
<Typography component="h1" variant="h5">
二步验证
{t("login.2FA")}
</Typography>
<form className={classes.form} onSubmit={twoFALogin}>
<FormControl margin="normal" required fullWidth>
<InputLabel htmlFor="code">
请输入六位二步验证代码
{t("login.input2FACode")}
</InputLabel>
<Input
id="code"
@ -419,7 +439,7 @@ function LoginForm() {
disabled={loading}
className={classes.submit}
>
继续登录
{t("login.continue")}
</Button>{" "}
</form>{" "}
<Divider />

View File

@ -1,5 +1,6 @@
import React from "react";
import { withStyles } from "@material-ui/core";
import { withTranslation } from "react-i18next";
const styles = {
h1: {
@ -18,7 +19,6 @@ class ErrorBoundary extends React.Component {
}
static getDerivedStateFromError() {
// 更新 state 使下一次渲染能够显示降级后的 UI
return { hasError: true };
}
@ -30,20 +30,17 @@ class ErrorBoundary extends React.Component {
}
render() {
const { classes } = this.props;
const { classes, t } = this.props;
if (this.state.hasError) {
// 你可以自定义降级后的 UI 并渲染
return (
<>
<h1 className={classes.h1}>:(</h1>
<h2 className={classes.h2}>
页面渲染出现错误请尝试刷新此页面
</h2>
<h2 className={classes.h2}>{t("renderError")}</h2>
{this.state.error &&
this.state.errorInfo &&
this.state.errorInfo.componentStack && (
<details>
<summary>错误详情</summary>
<summary>{t("errorDetails")}</summary>
<pre>
<code>{this.state.error.toString()}</code>
</pre>
@ -62,4 +59,4 @@ class ErrorBoundary extends React.Component {
}
}
export default withStyles(styles)(ErrorBoundary);
export default withTranslation(["common"])(withStyles(styles)(ErrorBoundary));

21
src/i18n.ts Normal file
View File

@ -0,0 +1,21 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import Backend from "i18next-http-backend";
import LanguageDetector from "i18next-browser-languagedetector";
i18n.use(Backend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: "en-US",
debug: true,
ns: ["common", "application"],
load: "currentOnly",
defaultNS: "application",
interpolation: {
escapeValue: false,
},
});
export default i18n;

View File

@ -11,16 +11,16 @@ import { UpdateSiteConfig } from "./middleware/Init";
import ErrorBoundary from "./component/Placeholder/ErrorBoundary";
import { createBrowserHistory } from "history";
import { ConnectedRouter, routerMiddleware } from "connected-react-router";
import i18next from "./i18n";
const Admin = React.lazy(() => import("./Admin"));
if (window.location.hash !== "") {
window.location.href = window.location.hash.split("#")[1];
}
serviceWorker.register({
onUpdate: (registration) => {
alert("当前页面有新版本可用,准备刷新。");
alert(i18next.t("newVersionRefresh", { ns: "common" }));
if (registration && registration.waiting) {
registration.waiting.postMessage({ type: "SKIP_WAITING" });
}
@ -41,21 +41,21 @@ const store = createStore(cloureveApp(history), reduxEnhance);
UpdateSiteConfig(store);
ReactDOM.render(
<ErrorBoundary>
<Provider store={store}>
<ConnectedRouter history={history}>
<Switch>
<Route path="/admin">
<Suspense fallback={"Loading..."}>
<Suspense fallback={"Loading..."}>
<ErrorBoundary>
<Provider store={store}>
<ConnectedRouter history={history}>
<Switch>
<Route path="/admin">
<Admin />
</Suspense>
</Route>
<Route exact path="">
<App />
</Route>
</Switch>
</ConnectedRouter>
</Provider>
</ErrorBoundary>,
</Route>
<Route exact path="">
<App />
</Route>
</Switch>
</ConnectedRouter>
</Provider>
</ErrorBoundary>
</Suspense>,
document.getElementById("root")
);

View File

@ -1,5 +1,6 @@
import axios from "axios";
import Auth from "./Auth";
import i18next from "../i18n";
export const baseURL = "/api/v3";
@ -34,7 +35,16 @@ class AppError extends Error {
constructor(message: string | undefined, public code: any, error: any) {
super(message);
this.code = code;
this.message = message || "未知错误";
if (i18next.exists(`errors.${code}`, { ns: "common" })) {
this.message = i18next.t(`errors.${code}`, {
ns: "common",
message,
});
} else {
this.message =
message || i18next.t("unknownError", { ns: "common" });
}
this.message += error ? " " + error : "";
this.stack = new Error().stack;
}
@ -49,13 +59,13 @@ instance.interceptors.response.use(
response.rawData.code !== 0 &&
response.rawData.code !== 203
) {
// 登录过期
// Login expired
if (response.rawData.code === 401) {
Auth.signout();
window.location.href = "/login";
}
// 非管理员
// Non-admin
if (response.rawData.code === 40008) {
window.location.href = "/home";
}

View File

@ -6,6 +6,7 @@ import {
setSiteConfig,
toggleSnackbar,
} from "../redux/explorer";
import i18next from "../i18n";
const initUserConfig = (siteConfig) => {
if (siteConfig.user !== undefined && !siteConfig.user.anonymous) {
@ -81,7 +82,8 @@ export async function UpdateSiteConfig(store) {
toggleSnackbar(
"top",
"right",
"无法加载站点配置:" + error.message,
i18next.t("errLoadingSiteConfig", { ns: "common" }) +
error.message,
"error"
)
);

View File

@ -1173,6 +1173,13 @@
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.14.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.17.2":
version "7.17.9"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.9.tgz#d19fbf802d01a8cb6cf053a64e472d42c434ba72"
integrity sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/template@^7.16.7", "@babel/template@^7.4.0", "@babel/template@^7.6.0", "@babel/template@^7.8.6":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155"
@ -3599,6 +3606,13 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
cross-fetch@3.1.5:
version "3.1.5"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f"
integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==
dependencies:
node-fetch "2.6.7"
cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5:
version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
@ -5642,7 +5656,7 @@ html-entities@^1.3.1:
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.4.0.tgz#cfbd1b01d2afaf9adca1b10ae7dffab98c71d2dc"
integrity sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA==
html-escaper@^2.0.0:
html-escaper@^2.0.0, html-escaper@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
@ -5660,6 +5674,13 @@ html-minifier@^3.5.20:
relateurl "0.2.x"
uglify-js "3.4.x"
html-parse-stringify@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2"
integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==
dependencies:
void-elements "3.1.0"
html-webpack-plugin@4.0.0-beta.5:
version "4.0.0-beta.5"
resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-4.0.0-beta.5.tgz#2c53083c1151bfec20479b1f8aaf0039e77b5513"
@ -5777,6 +5798,27 @@ hyphenate-style-name@^1.0.3:
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d"
integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==
i18next-browser-languagedetector@^6.1.4:
version "6.1.4"
resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-6.1.4.tgz#7b087c5edb6f6acd38ef54ede2160ab9cde0108f"
integrity sha512-wukWnFeU7rKIWT66VU5i8I+3Zc4wReGcuDK2+kuFhtoxBRGWGdvYI9UQmqNL/yQH1KogWwh+xGEaIPH8V/i2Zg==
dependencies:
"@babel/runtime" "^7.14.6"
i18next-http-backend@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/i18next-http-backend/-/i18next-http-backend-1.4.0.tgz#e6abef0615316e00837798d2385c13395096f963"
integrity sha512-wsvx7E/CT1pHmBM99Vu57YLJpsrHbVjxGxf25EIJ/6oTjsvCkZZ6c3SA4TejcK5jIHfv9oLxQX8l+DFKZHZ0Gg==
dependencies:
cross-fetch "3.1.5"
i18next@^21.6.16:
version "21.6.16"
resolved "https://registry.yarnpkg.com/i18next/-/i18next-21.6.16.tgz#8cff8c3ba2ffaf8438a8c83fe284083f15cf3941"
integrity sha512-xJlzrVxG9CyAGsbMP1aKuiNr1Ed2m36KiTB7hjGMG2Zo4idfw3p9THUEu+GjBwIgEZ7F11ZbCzJcfv4uyfKNuw==
dependencies:
"@babel/runtime" "^7.17.2"
iconv-lite@0.4.24, iconv-lite@^0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@ -7806,6 +7848,13 @@ node-ensure@^0.0.0:
resolved "https://registry.yarnpkg.com/node-ensure/-/node-ensure-0.0.0.tgz#ecae764150de99861ec5c810fd5d096b183932a7"
integrity sha1-7K52QVDemYYexcgQ/V0Jaxg5Mqc=
node-fetch@2.6.7:
version "2.6.7"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
dependencies:
whatwg-url "^5.0.0"
node-forge@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
@ -9564,6 +9613,15 @@ react-hotkeys@^2.0.0:
dependencies:
prop-types "^15.6.1"
react-i18next@^11.16.7:
version "11.16.7"
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.16.7.tgz#8d0680b7f4c8e43f59996336b7183ad576a28df7"
integrity sha512-7yotILJLnKfvUfrl/nt9eK9vFpVFjZPLWAwBzWL6XppSZZEvlmlKk0GBGDCAPfLfs8oND7WAbry8wGzdoiW5Nw==
dependencies:
"@babel/runtime" "^7.14.5"
html-escaper "^2.0.2"
html-parse-stringify "^3.0.1"
react-is@^16.10.2, react-is@^16.12.0, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.4:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@ -11252,6 +11310,11 @@ tr46@^1.0.1:
dependencies:
punycode "^2.1.0"
tr46@~0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=
ts-pnp@1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.1.4.tgz#ae27126960ebaefb874c6d7fa4729729ab200d90"
@ -11596,6 +11659,11 @@ vm-browserify@^1.0.1:
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
void-elements@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
integrity sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=
w3c-hr-time@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"
@ -11644,6 +11712,11 @@ wbuf@^1.1.0, wbuf@^1.7.3:
dependencies:
minimalistic-assert "^1.0.0"
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=
webidl-conversions@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
@ -11785,6 +11858,14 @@ whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0:
resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"
integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
whatwg-url@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0=
dependencies:
tr46 "~0.0.3"
webidl-conversions "^3.0.0"
whatwg-url@^6.4.1:
version "6.5.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8"