mirror of
https://github.com/LemmyNet/lemmy-ui.git
synced 2024-11-22 20:31:13 +00:00
Merge branch 'LemmyNet:main' into userpage
This commit is contained in:
commit
03e0e2d1c6
30 changed files with 944 additions and 558 deletions
|
@ -16,6 +16,7 @@
|
|||
"warnOnUnsupportedTypeScriptVersion": false
|
||||
},
|
||||
"rules": {
|
||||
"@typescript-eslint/ban-ts-comment": 0,
|
||||
"@typescript-eslint/no-explicit-any": 0,
|
||||
"@typescript-eslint/explicit-module-boundary-types": 0,
|
||||
"arrow-body-style": 0,
|
||||
|
|
|
@ -7,5 +7,9 @@ new_tag="$1"
|
|||
# sudo docker build . --tag dessalines/lemmy-ui:$new_tag
|
||||
# sudo docker push dessalines/lemmy-ui:$new_tag
|
||||
|
||||
# Upgrade version
|
||||
yarn version --new-version $new_tag
|
||||
git push
|
||||
|
||||
git tag $new_tag
|
||||
git push origin $new_tag
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit eee933bd87780e0e2a8700e9b8fe0047f14f428a
|
||||
Subproject commit 7dd7b98da76477222f9fd9720b4b25e14e3ddc97
|
21
package.json
21
package.json
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"name": "lemmy-ui",
|
||||
"description": "An isomorphic UI for lemmy",
|
||||
"version": "0.12.2",
|
||||
"author": "Dessalines <tyhou13@gmx.com>",
|
||||
"license": "AGPL-3.0",
|
||||
"scripts": {
|
||||
|
@ -15,23 +16,25 @@
|
|||
},
|
||||
"repository": "https://github.com/LemmyNet/lemmy-ui",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/parser": "^4.28.3",
|
||||
"@typescript-eslint/parser": "^4.31.1",
|
||||
"autosize": "^5.0.1",
|
||||
"check-password-strength": "^2.0.3",
|
||||
"choices.js": "^9.0.1",
|
||||
"emoji-short-name": "^1.0.0",
|
||||
"express": "~4.17.1",
|
||||
"i18next": "^20.3.3",
|
||||
"inferno": "^7.4.9",
|
||||
"inferno-create-element": "^7.4.9",
|
||||
"i18next": "^20.6.1",
|
||||
"inferno": "^7.4.10",
|
||||
"inferno-create-element": "^7.4.10",
|
||||
"inferno-helmet": "^5.2.1",
|
||||
"inferno-hydrate": "^7.4.9",
|
||||
"inferno-i18next": "github:nimbusec-oss/inferno-i18next#semver:^7.4.2",
|
||||
"inferno-router": "^7.4.9",
|
||||
"inferno-server": "^7.4.9",
|
||||
"inferno-hydrate": "^7.4.10",
|
||||
"inferno-i18next-dess": "^0.0.1",
|
||||
"inferno-router": "^7.4.10",
|
||||
"inferno-server": "^7.4.10",
|
||||
"isomorphic-cookie": "^1.2.4",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"markdown-it": "^12.1.0",
|
||||
"markdown-it-container": "^3.0.0",
|
||||
"markdown-it-html5-embed": "^1.0.0",
|
||||
"markdown-it-sub": "^1.0.0",
|
||||
"markdown-it-sup": "^1.0.0",
|
||||
"moment": "^2.29.1",
|
||||
|
@ -42,7 +45,7 @@
|
|||
"tippy.js": "^6.3.1",
|
||||
"toastify-js": "^1.11.1",
|
||||
"tributejs": "^5.1.3",
|
||||
"ws": "^8.2.0"
|
||||
"ws": "^8.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.14.6",
|
||||
|
|
|
@ -207,6 +207,10 @@ hr {
|
|||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.overflow-wrap-anywhere {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
#app {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Component } from "inferno";
|
||||
import { Helmet } from "inferno-helmet";
|
||||
import { Provider } from "inferno-i18next";
|
||||
import { Provider } from "inferno-i18next-dess";
|
||||
import { Route, Switch } from "inferno-router";
|
||||
import { GetSiteResponse } from "lemmy-js-client";
|
||||
import { i18n } from "../../i18next";
|
||||
|
|
|
@ -20,14 +20,15 @@ import { i18n } from "../../i18next";
|
|||
import { UserService, WebSocketService } from "../../services";
|
||||
import {
|
||||
authField,
|
||||
donateLemmyUrl,
|
||||
fetchLimit,
|
||||
getLanguage,
|
||||
isBrowser,
|
||||
notifyComment,
|
||||
notifyPrivateMessage,
|
||||
numToSI,
|
||||
setTheme,
|
||||
showAvatars,
|
||||
supportLemmyUrl,
|
||||
toast,
|
||||
wsClient,
|
||||
wsJsonToRes,
|
||||
|
@ -189,7 +190,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
|||
"unread_messages"
|
||||
)}`}
|
||||
>
|
||||
{this.state.unreadCount}
|
||||
{numToSI(this.state.unreadCount)}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
|
@ -240,7 +241,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
|||
<a
|
||||
className="nav-link"
|
||||
title={i18n.t("support_lemmy")}
|
||||
href={supportLemmyUrl}
|
||||
href={donateLemmyUrl}
|
||||
>
|
||||
<Icon icon="heart" classes="small" />
|
||||
</a>
|
||||
|
@ -263,7 +264,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
|||
/^\/search/
|
||||
) && (
|
||||
<form
|
||||
class="form-inline"
|
||||
class="form-inline mr-2"
|
||||
onSubmit={linkEvent(this, this.handleSearchSubmit)}
|
||||
>
|
||||
<input
|
||||
|
@ -309,7 +310,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
|||
"unread_messages"
|
||||
)}`}
|
||||
>
|
||||
{this.state.unreadCount}
|
||||
{numToSI(this.state.unreadCount)}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
|
@ -375,13 +376,22 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
|||
</>
|
||||
) : (
|
||||
<ul class="navbar-nav my-2">
|
||||
<li className="ml-2 nav-item">
|
||||
<li className="nav-item">
|
||||
<button
|
||||
className="btn btn-success"
|
||||
className="nav-link btn btn-link"
|
||||
onClick={linkEvent(this, this.handleGotoLogin)}
|
||||
title={i18n.t("login_sign_up")}
|
||||
title={i18n.t("login")}
|
||||
>
|
||||
{i18n.t("login_sign_up")}
|
||||
{i18n.t("login")}
|
||||
</button>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<button
|
||||
className="nav-link btn btn-link"
|
||||
onClick={linkEvent(this, this.handleGotoSignup)}
|
||||
title={i18n.t("sign_up")}
|
||||
>
|
||||
{i18n.t("sign_up")}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -428,7 +438,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
|||
handleLogoutClick(i: Navbar) {
|
||||
i.setState({ showDropdown: false, expanded: false });
|
||||
UserService.Instance.logout();
|
||||
i.context.router.history.push("/");
|
||||
window.location.href = "/";
|
||||
location.reload();
|
||||
}
|
||||
|
||||
|
@ -481,6 +491,11 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
|||
i.context.router.history.push(`/login`);
|
||||
}
|
||||
|
||||
handleGotoSignup(i: Navbar) {
|
||||
i.setState({ showDropdown: false, expanded: false });
|
||||
i.context.router.history.push(`/signup`);
|
||||
}
|
||||
|
||||
handleShowDropdown(i: Navbar) {
|
||||
i.state.showDropdown = !i.state.showDropdown;
|
||||
i.setState(i.state);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Component } from "inferno";
|
||||
import { T } from "inferno-i18next";
|
||||
import { T } from "inferno-i18next-dess";
|
||||
import { Link } from "inferno-router";
|
||||
import {
|
||||
CommentResponse,
|
||||
|
|
|
@ -30,6 +30,7 @@ import {
|
|||
getUnixTime,
|
||||
isMod,
|
||||
mdToHtml,
|
||||
numToSI,
|
||||
setupTippy,
|
||||
showScores,
|
||||
wsClient,
|
||||
|
@ -217,9 +218,10 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
class="mr-1 font-weight-bold"
|
||||
aria-label={i18n.t("number_of_points", {
|
||||
count: this.state.score,
|
||||
formattedCount: this.state.score,
|
||||
})}
|
||||
>
|
||||
{this.state.score}
|
||||
{numToSI(this.state.score)}
|
||||
</span>
|
||||
</a>
|
||||
<span className="mr-1">•</span>
|
||||
|
@ -293,7 +295,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
<Icon icon="arrow-up1" classes="icon-inline" />
|
||||
{showScores() &&
|
||||
this.state.upvotes !== this.state.score && (
|
||||
<span class="ml-1">{this.state.upvotes}</span>
|
||||
<span class="ml-1">
|
||||
{numToSI(this.state.upvotes)}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
{this.props.enableDownvotes && (
|
||||
|
@ -310,7 +314,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
<Icon icon="arrow-down1" classes="icon-inline" />
|
||||
{showScores() &&
|
||||
this.state.upvotes !== this.state.score && (
|
||||
<span class="ml-1">{this.state.downvotes}</span>
|
||||
<span class="ml-1">
|
||||
{numToSI(this.state.downvotes)}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
|
@ -1289,14 +1295,17 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
get pointsTippy(): string {
|
||||
let points = i18n.t("number_of_points", {
|
||||
count: this.state.score,
|
||||
formattedCount: this.state.score,
|
||||
});
|
||||
|
||||
let upvotes = i18n.t("number_of_upvotes", {
|
||||
count: this.state.upvotes,
|
||||
formattedCount: this.state.upvotes,
|
||||
});
|
||||
|
||||
let downvotes = i18n.t("number_of_downvotes", {
|
||||
count: this.state.downvotes,
|
||||
formattedCount: this.state.downvotes,
|
||||
});
|
||||
|
||||
return `${points} • ${upvotes} • ${downvotes}`;
|
||||
|
|
|
@ -36,7 +36,7 @@ export class ImageUploadForm extends Component<
|
|||
<form class="d-inline">
|
||||
<label
|
||||
htmlFor={this.id}
|
||||
class="pointer ml-4 text-muted small font-weight-bold"
|
||||
class="pointer text-muted small font-weight-bold"
|
||||
>
|
||||
{!this.props.imageSrc ? (
|
||||
<span class="btn btn-secondary">{this.props.uploadTitle}</span>
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
getListingTypeFromPropsNoDefault,
|
||||
getPageFromProps,
|
||||
isBrowser,
|
||||
numToSI,
|
||||
setIsoData,
|
||||
setOptionalAuth,
|
||||
showLocal,
|
||||
|
@ -160,13 +161,17 @@ export class Communities extends Component<any, CommunitiesState> {
|
|||
<td>
|
||||
<CommunityLink community={cv.community} />
|
||||
</td>
|
||||
<td class="text-right">{cv.counts.subscribers}</td>
|
||||
<td class="text-right">{cv.counts.users_active_month}</td>
|
||||
<td class="text-right d-none d-lg-table-cell">
|
||||
{cv.counts.posts}
|
||||
<td class="text-right">
|
||||
{numToSI(cv.counts.subscribers)}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{numToSI(cv.counts.users_active_month)}
|
||||
</td>
|
||||
<td class="text-right d-none d-lg-table-cell">
|
||||
{cv.counts.comments}
|
||||
{numToSI(cv.counts.posts)}
|
||||
</td>
|
||||
<td class="text-right d-none d-lg-table-cell">
|
||||
{numToSI(cv.counts.comments)}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{cv.subscribed ? (
|
||||
|
|
|
@ -121,16 +121,19 @@ export class CommunityForm extends Component<
|
|||
<form onSubmit={linkEvent(this, this.handleCreateCommunitySubmit)}>
|
||||
{!this.props.community_view && (
|
||||
<div class="form-group row">
|
||||
<label class="col-12 col-form-label" htmlFor="community-name">
|
||||
<label
|
||||
class="col-12 col-sm-2 col-form-label"
|
||||
htmlFor="community-name"
|
||||
>
|
||||
{i18n.t("name")}
|
||||
<span
|
||||
class="pointer unselectable ml-2 text-muted"
|
||||
class="position-absolute pointer unselectable ml-2 text-muted"
|
||||
data-tippy-content={i18n.t("name_explain")}
|
||||
>
|
||||
<Icon icon="help-circle" classes="icon-inline" />
|
||||
</span>
|
||||
</label>
|
||||
<div class="col-12">
|
||||
<div class="col-12 col-sm-10">
|
||||
<input
|
||||
type="text"
|
||||
id="community-name"
|
||||
|
@ -146,16 +149,19 @@ export class CommunityForm extends Component<
|
|||
</div>
|
||||
)}
|
||||
<div class="form-group row">
|
||||
<label class="col-12 col-form-label" htmlFor="community-title">
|
||||
<label
|
||||
class="col-12 col-sm-2 col-form-label"
|
||||
htmlFor="community-title"
|
||||
>
|
||||
{i18n.t("display_name")}
|
||||
<span
|
||||
class="pointer unselectable ml-2 text-muted"
|
||||
class="position-absolute pointer unselectable ml-2 text-muted"
|
||||
data-tippy-content={i18n.t("display_name_explain")}
|
||||
>
|
||||
<Icon icon="help-circle" classes="icon-inline" />
|
||||
</span>
|
||||
</label>
|
||||
<div class="col-12">
|
||||
<div class="col-12 col-sm-10">
|
||||
<input
|
||||
type="text"
|
||||
id="community-title"
|
||||
|
@ -168,30 +174,34 @@ export class CommunityForm extends Component<
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>{i18n.t("icon")}</label>
|
||||
<ImageUploadForm
|
||||
uploadTitle={i18n.t("upload_icon")}
|
||||
imageSrc={this.state.communityForm.icon}
|
||||
onUpload={this.handleIconUpload}
|
||||
onRemove={this.handleIconRemove}
|
||||
rounded
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>{i18n.t("banner")}</label>
|
||||
<ImageUploadForm
|
||||
uploadTitle={i18n.t("upload_banner")}
|
||||
imageSrc={this.state.communityForm.banner}
|
||||
onUpload={this.handleBannerUpload}
|
||||
onRemove={this.handleBannerRemove}
|
||||
/>
|
||||
<div class="form-group row">
|
||||
<label class="col-12 col-sm-2">{i18n.t("icon")}</label>
|
||||
<div class="col-12 col-sm-10">
|
||||
<ImageUploadForm
|
||||
uploadTitle={i18n.t("upload_icon")}
|
||||
imageSrc={this.state.communityForm.icon}
|
||||
onUpload={this.handleIconUpload}
|
||||
onRemove={this.handleIconRemove}
|
||||
rounded
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-12 col-form-label" htmlFor={this.id}>
|
||||
<label class="col-12 col-sm-2">{i18n.t("banner")}</label>
|
||||
<div class="col-12 col-sm-10">
|
||||
<ImageUploadForm
|
||||
uploadTitle={i18n.t("upload_banner")}
|
||||
imageSrc={this.state.communityForm.banner}
|
||||
onUpload={this.handleBannerUpload}
|
||||
onRemove={this.handleBannerRemove}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-12 col-sm-2 col-form-label" htmlFor={this.id}>
|
||||
{i18n.t("sidebar")}
|
||||
</label>
|
||||
<div class="col-12">
|
||||
<div class="col-12 col-sm-10">
|
||||
<MarkdownTextArea
|
||||
initialContent={this.state.communityForm.description}
|
||||
onContentChange={this.handleCommunityDescriptionChange}
|
||||
|
@ -201,18 +211,18 @@ export class CommunityForm extends Component<
|
|||
|
||||
{this.props.enableNsfw && (
|
||||
<div class="form-group row">
|
||||
<div class="col-12">
|
||||
<legend class="col-form-label col-sm-2 pt-0">
|
||||
{i18n.t("nsfw")}
|
||||
</legend>
|
||||
<div class="col-10">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
class="form-check-input position-static"
|
||||
id="community-nsfw"
|
||||
type="checkbox"
|
||||
checked={this.state.communityForm.nsfw}
|
||||
onChange={linkEvent(this, this.handleCommunityNsfwChange)}
|
||||
/>
|
||||
<label class="form-check-label" htmlFor="community-nsfw">
|
||||
{i18n.t("nsfw")}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -61,7 +61,7 @@ export class CommunityLink extends Component<CommunityLinkProps, any> {
|
|||
{!this.props.hideAvatar && community.icon && showAvatars() && (
|
||||
<PictrsImage src={community.icon} icon />
|
||||
)}
|
||||
<span>{displayName}</span>
|
||||
<span class="overflow-wrap-anywhere">{displayName}</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -336,7 +336,7 @@ export class Community extends Component<any, State> {
|
|||
return (
|
||||
<div class="mb-2">
|
||||
<BannerIconHeader banner={community.banner} icon={community.icon} />
|
||||
<h5 class="mb-0">{community.title}</h5>
|
||||
<h5 class="mb-0 overflow-wrap-anywhere">{community.title}</h5>
|
||||
<CommunityLink
|
||||
community={community}
|
||||
realLink
|
||||
|
|
|
@ -11,7 +11,13 @@ import {
|
|||
} from "lemmy-js-client";
|
||||
import { i18n } from "../../i18next";
|
||||
import { UserService, WebSocketService } from "../../services";
|
||||
import { authField, getUnixTime, mdToHtml, wsClient } from "../../utils";
|
||||
import {
|
||||
authField,
|
||||
getUnixTime,
|
||||
mdToHtml,
|
||||
numToSI,
|
||||
wsClient,
|
||||
} from "../../utils";
|
||||
import { BannerIconHeader } from "../common/banner-icon-header";
|
||||
import { Icon } from "../common/icon";
|
||||
import { CommunityForm } from "../community/community-form";
|
||||
|
@ -143,67 +149,79 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
|||
return (
|
||||
<ul class="my-1 list-inline">
|
||||
<li className="list-inline-item badge badge-secondary">
|
||||
{i18n.t("number_online", { count: this.props.online })}
|
||||
{i18n.t("number_online", {
|
||||
count: this.props.online,
|
||||
formattedCount: numToSI(this.props.online),
|
||||
})}
|
||||
</li>
|
||||
<li
|
||||
className="list-inline-item badge badge-secondary pointer"
|
||||
data-tippy-content={`${i18n.t("number_of_users", {
|
||||
data-tippy-content={i18n.t("active_users_in_the_last_day", {
|
||||
count: counts.users_active_day,
|
||||
})} ${i18n.t("active_in_the_last")} ${i18n.t("day")}`}
|
||||
formattedCount: counts.users_active_day,
|
||||
})}
|
||||
>
|
||||
{i18n.t("number_of_users", {
|
||||
count: counts.users_active_day,
|
||||
formattedCount: numToSI(counts.users_active_day),
|
||||
})}{" "}
|
||||
/ {i18n.t("day")}
|
||||
</li>
|
||||
<li
|
||||
className="list-inline-item badge badge-secondary pointer"
|
||||
data-tippy-content={`${i18n.t("number_of_users", {
|
||||
data-tippy-content={i18n.t("active_users_in_the_last_week", {
|
||||
count: counts.users_active_week,
|
||||
})} ${i18n.t("active_in_the_last")} ${i18n.t("week")}`}
|
||||
formattedCount: counts.users_active_week,
|
||||
})}
|
||||
>
|
||||
{i18n.t("number_of_users", {
|
||||
count: counts.users_active_week,
|
||||
formattedCount: numToSI(counts.users_active_week),
|
||||
})}{" "}
|
||||
/ {i18n.t("week")}
|
||||
</li>
|
||||
<li
|
||||
className="list-inline-item badge badge-secondary pointer"
|
||||
data-tippy-content={`${i18n.t("number_of_users", {
|
||||
data-tippy-content={i18n.t("active_users_in_the_last_month", {
|
||||
count: counts.users_active_month,
|
||||
})} ${i18n.t("active_in_the_last")} ${i18n.t("month")}`}
|
||||
formattedCount: counts.users_active_month,
|
||||
})}
|
||||
>
|
||||
{i18n.t("number_of_users", {
|
||||
count: counts.users_active_month,
|
||||
formattedCount: numToSI(counts.users_active_month),
|
||||
})}{" "}
|
||||
/ {i18n.t("month")}
|
||||
</li>
|
||||
<li
|
||||
className="list-inline-item badge badge-secondary pointer"
|
||||
data-tippy-content={`${i18n.t("number_of_users", {
|
||||
data-tippy-content={i18n.t("active_users_in_the_last_six_months", {
|
||||
count: counts.users_active_half_year,
|
||||
})} ${i18n.t("active_in_the_last")} ${i18n.t("number_of_months", {
|
||||
count: 6,
|
||||
})}`}
|
||||
formattedCount: counts.users_active_half_year,
|
||||
})}
|
||||
>
|
||||
{i18n.t("number_of_users", {
|
||||
count: counts.users_active_half_year,
|
||||
formattedCount: numToSI(counts.users_active_half_year),
|
||||
})}{" "}
|
||||
/ {i18n.t("number_of_months", { count: 6 })}
|
||||
/ {i18n.t("number_of_months", { count: 6, formattedCount: 6 })}
|
||||
</li>
|
||||
<li className="list-inline-item badge badge-secondary">
|
||||
{i18n.t("number_of_subscribers", {
|
||||
count: counts.subscribers,
|
||||
formattedCount: numToSI(counts.subscribers),
|
||||
})}
|
||||
</li>
|
||||
<li className="list-inline-item badge badge-secondary">
|
||||
{i18n.t("number_of_posts", {
|
||||
count: counts.posts,
|
||||
formattedCount: numToSI(counts.posts),
|
||||
})}
|
||||
</li>
|
||||
<li className="list-inline-item badge badge-secondary">
|
||||
{i18n.t("number_of_comments", {
|
||||
count: counts.comments,
|
||||
formattedCount: numToSI(counts.comments),
|
||||
})}
|
||||
</li>
|
||||
<li className="list-inline-item">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Component, linkEvent } from "inferno";
|
||||
import { T } from "inferno-i18next";
|
||||
import { T } from "inferno-i18next-dess";
|
||||
import { Link } from "inferno-router";
|
||||
import {
|
||||
AddAdminResponse,
|
||||
|
@ -40,6 +40,7 @@ import {
|
|||
getSortTypeFromProps,
|
||||
mdToHtml,
|
||||
notifyPost,
|
||||
numToSI,
|
||||
restoreScrollPosition,
|
||||
saveCommentRes,
|
||||
saveScrollPosition,
|
||||
|
@ -503,72 +504,85 @@ export class Home extends Component<any, HomeState> {
|
|||
return (
|
||||
<ul class="my-2 list-inline">
|
||||
<li className="list-inline-item badge badge-secondary">
|
||||
{i18n.t("number_online", { count: this.state.siteRes.online })}
|
||||
{i18n.t("number_online", {
|
||||
count: this.state.siteRes.online,
|
||||
formattedCount: numToSI(this.state.siteRes.online),
|
||||
})}
|
||||
</li>
|
||||
<li
|
||||
className="list-inline-item badge badge-secondary pointer"
|
||||
data-tippy-content={`${i18n.t("number_of_users", {
|
||||
data-tippy-content={i18n.t("active_users_in_the_last_day", {
|
||||
count: counts.users_active_day,
|
||||
})} ${i18n.t("active_in_the_last")} ${i18n.t("day")}`}
|
||||
formattedCount: numToSI(counts.users_active_day),
|
||||
})}
|
||||
>
|
||||
{i18n.t("number_of_users", {
|
||||
count: counts.users_active_day,
|
||||
formattedCount: numToSI(counts.users_active_day),
|
||||
})}{" "}
|
||||
/ {i18n.t("day")}
|
||||
</li>
|
||||
<li
|
||||
className="list-inline-item badge badge-secondary pointer"
|
||||
data-tippy-content={`${i18n.t("number_of_users", {
|
||||
data-tippy-content={i18n.t("active_users_in_the_last_week", {
|
||||
count: counts.users_active_week,
|
||||
})} ${i18n.t("active_in_the_last")} ${i18n.t("week")}`}
|
||||
formattedCount: counts.users_active_week,
|
||||
})}
|
||||
>
|
||||
{i18n.t("number_of_users", {
|
||||
count: counts.users_active_week,
|
||||
formattedCount: numToSI(counts.users_active_week),
|
||||
})}{" "}
|
||||
/ {i18n.t("week")}
|
||||
</li>
|
||||
<li
|
||||
className="list-inline-item badge badge-secondary pointer"
|
||||
data-tippy-content={`${i18n.t("number_of_users", {
|
||||
data-tippy-content={i18n.t("active_users_in_the_last_month", {
|
||||
count: counts.users_active_month,
|
||||
})} ${i18n.t("active_in_the_last")} ${i18n.t("month")}`}
|
||||
formattedCount: counts.users_active_month,
|
||||
})}
|
||||
>
|
||||
{i18n.t("number_of_users", {
|
||||
count: counts.users_active_month,
|
||||
formattedCount: numToSI(counts.users_active_month),
|
||||
})}{" "}
|
||||
/ {i18n.t("month")}
|
||||
</li>
|
||||
<li
|
||||
className="list-inline-item badge badge-secondary pointer"
|
||||
data-tippy-content={`${i18n.t("number_of_users", {
|
||||
data-tippy-content={i18n.t("active_users_in_the_last_six_months", {
|
||||
count: counts.users_active_half_year,
|
||||
})} ${i18n.t("active_in_the_last")} ${i18n.t("number_of_months", {
|
||||
count: 6,
|
||||
})}`}
|
||||
formattedCount: counts.users_active_half_year,
|
||||
})}
|
||||
>
|
||||
{i18n.t("number_of_users", {
|
||||
count: counts.users_active_half_year,
|
||||
formattedCount: numToSI(counts.users_active_half_year),
|
||||
})}{" "}
|
||||
/ {i18n.t("number_of_months", { count: 6 })}
|
||||
/ {i18n.t("number_of_months", { count: 6, formattedCount: 6 })}
|
||||
</li>
|
||||
<li className="list-inline-item badge badge-secondary">
|
||||
{i18n.t("number_of_users", {
|
||||
count: counts.users,
|
||||
formattedCount: numToSI(counts.users),
|
||||
})}
|
||||
</li>
|
||||
<li className="list-inline-item badge badge-secondary">
|
||||
{i18n.t("number_of_communities", {
|
||||
count: counts.communities,
|
||||
formattedCount: numToSI(counts.communities),
|
||||
})}
|
||||
</li>
|
||||
<li className="list-inline-item badge badge-secondary">
|
||||
{i18n.t("number_of_posts", {
|
||||
count: counts.posts,
|
||||
formattedCount: numToSI(counts.posts),
|
||||
})}
|
||||
</li>
|
||||
<li className="list-inline-item badge badge-secondary">
|
||||
{i18n.t("number_of_comments", {
|
||||
count: counts.comments,
|
||||
formattedCount: numToSI(counts.comments),
|
||||
})}
|
||||
</li>
|
||||
<li className="list-inline-item">
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
import { Component, linkEvent } from "inferno";
|
||||
import { T } from "inferno-i18next";
|
||||
import {
|
||||
GetCaptchaResponse,
|
||||
GetSiteResponse,
|
||||
Login as LoginForm,
|
||||
LoginResponse,
|
||||
PasswordReset,
|
||||
Register,
|
||||
SiteView,
|
||||
UserOperation,
|
||||
} from "lemmy-js-client";
|
||||
|
@ -16,7 +13,6 @@ import { UserService, WebSocketService } from "../../services";
|
|||
import {
|
||||
authField,
|
||||
isBrowser,
|
||||
joinLemmyUrl,
|
||||
setIsoData,
|
||||
toast,
|
||||
validEmail,
|
||||
|
@ -26,40 +22,24 @@ import {
|
|||
wsUserOp,
|
||||
} from "../../utils";
|
||||
import { HtmlTags } from "../common/html-tags";
|
||||
import { Icon, Spinner } from "../common/icon";
|
||||
import { Spinner } from "../common/icon";
|
||||
|
||||
interface State {
|
||||
loginForm: LoginForm;
|
||||
registerForm: Register;
|
||||
loginLoading: boolean;
|
||||
registerLoading: boolean;
|
||||
captcha: GetCaptchaResponse;
|
||||
captchaPlaying: boolean;
|
||||
site_view: SiteView;
|
||||
}
|
||||
|
||||
export class Login extends Component<any, State> {
|
||||
private isoData = setIsoData(this.context);
|
||||
private subscription: Subscription;
|
||||
private audio: HTMLAudioElement;
|
||||
|
||||
emptyState: State = {
|
||||
loginForm: {
|
||||
username_or_email: undefined,
|
||||
password: undefined,
|
||||
},
|
||||
registerForm: {
|
||||
username: undefined,
|
||||
password: undefined,
|
||||
password_verify: undefined,
|
||||
show_nsfw: false,
|
||||
captcha_uuid: undefined,
|
||||
captcha_answer: undefined,
|
||||
},
|
||||
loginLoading: false,
|
||||
registerLoading: false,
|
||||
captcha: undefined,
|
||||
captchaPlaying: false,
|
||||
site_view: this.isoData.site_res.site_view,
|
||||
};
|
||||
|
||||
|
@ -76,6 +56,13 @@ export class Login extends Component<any, State> {
|
|||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// Navigate to home if already logged in
|
||||
if (UserService.Instance.myUserInfo) {
|
||||
this.context.router.history.push("/");
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (isBrowser()) {
|
||||
this.subscription.unsubscribe();
|
||||
|
@ -98,8 +85,7 @@ export class Login extends Component<any, State> {
|
|||
path={this.context.router.route.match.url}
|
||||
/>
|
||||
<div class="row">
|
||||
<div class="col-12 col-lg-6 mb-4">{this.loginForm()}</div>
|
||||
<div class="col-12 col-lg-6">{this.registerForm()}</div>
|
||||
<div class="col-12 col-lg-6 offset-lg-3">{this.loginForm()}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -168,187 +154,6 @@ export class Login extends Component<any, State> {
|
|||
);
|
||||
}
|
||||
|
||||
registerForm() {
|
||||
return (
|
||||
<form onSubmit={linkEvent(this, this.handleRegisterSubmit)}>
|
||||
<h5>{i18n.t("sign_up")}</h5>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-2 col-form-label" htmlFor="register-username">
|
||||
{i18n.t("username")}
|
||||
</label>
|
||||
|
||||
<div class="col-sm-10">
|
||||
<input
|
||||
type="text"
|
||||
id="register-username"
|
||||
class="form-control"
|
||||
value={this.state.registerForm.username}
|
||||
onInput={linkEvent(this, this.handleRegisterUsernameChange)}
|
||||
required
|
||||
minLength={3}
|
||||
pattern="[a-zA-Z0-9_]+"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-2 col-form-label" htmlFor="register-email">
|
||||
{i18n.t("email")}
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<input
|
||||
type="email"
|
||||
id="register-email"
|
||||
class="form-control"
|
||||
placeholder={i18n.t("optional")}
|
||||
value={this.state.registerForm.email}
|
||||
autoComplete="email"
|
||||
onInput={linkEvent(this, this.handleRegisterEmailChange)}
|
||||
minLength={3}
|
||||
/>
|
||||
{!validEmail(this.state.registerForm.email) && (
|
||||
<div class="mt-2 mb-0 alert alert-light" role="alert">
|
||||
<Icon icon="alert-triangle" classes="icon-inline mr-2" />
|
||||
{i18n.t("no_password_reset")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-2 col-form-label" htmlFor="register-password">
|
||||
{i18n.t("password")}
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<input
|
||||
type="password"
|
||||
id="register-password"
|
||||
value={this.state.registerForm.password}
|
||||
autoComplete="new-password"
|
||||
onInput={linkEvent(this, this.handleRegisterPasswordChange)}
|
||||
maxLength={60}
|
||||
class="form-control"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label
|
||||
class="col-sm-2 col-form-label"
|
||||
htmlFor="register-verify-password"
|
||||
>
|
||||
{i18n.t("verify_password")}
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<input
|
||||
type="password"
|
||||
id="register-verify-password"
|
||||
value={this.state.registerForm.password_verify}
|
||||
autoComplete="new-password"
|
||||
onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)}
|
||||
maxLength={60}
|
||||
class="form-control"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{this.state.captcha && (
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-2" htmlFor="register-captcha">
|
||||
<span class="mr-2">{i18n.t("enter_code")}</span>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
onClick={linkEvent(this, this.handleRegenCaptcha)}
|
||||
aria-label={i18n.t("captcha")}
|
||||
>
|
||||
<Icon icon="refresh-cw" classes="icon-refresh-cw" />
|
||||
</button>
|
||||
</label>
|
||||
{this.showCaptcha()}
|
||||
<div class="col-sm-6">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="register-captcha"
|
||||
value={this.state.registerForm.captcha_answer}
|
||||
onInput={linkEvent(
|
||||
this,
|
||||
this.handleRegisterCaptchaAnswerChange
|
||||
)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{this.state.site_view.site.enable_nsfw && (
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-10">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
id="register-show-nsfw"
|
||||
type="checkbox"
|
||||
checked={this.state.registerForm.show_nsfw}
|
||||
onChange={linkEvent(this, this.handleRegisterShowNsfwChange)}
|
||||
/>
|
||||
<label class="form-check-label" htmlFor="register-show-nsfw">
|
||||
{i18n.t("show_nsfw")}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{this.isLemmyMl && (
|
||||
<div class="mt-2 mb-0 alert alert-light" role="alert">
|
||||
<T i18nKey="lemmy_ml_registration_message">
|
||||
#<a href={joinLemmyUrl}>#</a>
|
||||
</T>
|
||||
</div>
|
||||
)}
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-10">
|
||||
<button type="submit" class="btn btn-secondary">
|
||||
{this.state.registerLoading ? <Spinner /> : i18n.t("sign_up")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
showCaptcha() {
|
||||
return (
|
||||
<div class="col-sm-4">
|
||||
{this.state.captcha.ok && (
|
||||
<>
|
||||
<img
|
||||
class="rounded-top img-fluid"
|
||||
src={this.captchaPngSrc()}
|
||||
style="border-bottom-right-radius: 0; border-bottom-left-radius: 0;"
|
||||
alt={i18n.t("captcha")}
|
||||
/>
|
||||
{this.state.captcha.ok.wav && (
|
||||
<button
|
||||
class="rounded-bottom btn btn-sm btn-secondary btn-block"
|
||||
style="border-top-right-radius: 0; border-top-left-radius: 0;"
|
||||
title={i18n.t("play_captcha_audio")}
|
||||
onClick={linkEvent(this, this.handleCaptchaPlay)}
|
||||
type="button"
|
||||
disabled={this.state.captchaPlaying}
|
||||
>
|
||||
<Icon icon="play" classes="icon-play" />
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleLoginSubmit(i: Login, event: any) {
|
||||
event.preventDefault();
|
||||
i.state.loginLoading = true;
|
||||
|
@ -366,53 +171,6 @@ export class Login extends Component<any, State> {
|
|||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleRegisterSubmit(i: Login, event: any) {
|
||||
event.preventDefault();
|
||||
i.state.registerLoading = true;
|
||||
i.setState(i.state);
|
||||
WebSocketService.Instance.send(wsClient.register(i.state.registerForm));
|
||||
}
|
||||
|
||||
handleRegisterUsernameChange(i: Login, event: any) {
|
||||
i.state.registerForm.username = event.target.value;
|
||||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleRegisterEmailChange(i: Login, event: any) {
|
||||
i.state.registerForm.email = event.target.value;
|
||||
if (i.state.registerForm.email == "") {
|
||||
i.state.registerForm.email = undefined;
|
||||
}
|
||||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleRegisterPasswordChange(i: Login, event: any) {
|
||||
i.state.registerForm.password = event.target.value;
|
||||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleRegisterPasswordVerifyChange(i: Login, event: any) {
|
||||
i.state.registerForm.password_verify = event.target.value;
|
||||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleRegisterShowNsfwChange(i: Login, event: any) {
|
||||
i.state.registerForm.show_nsfw = event.target.checked;
|
||||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleRegisterCaptchaAnswerChange(i: Login, event: any) {
|
||||
i.state.registerForm.captcha_answer = event.target.value;
|
||||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleRegenCaptcha(i: Login) {
|
||||
i.audio = null;
|
||||
i.state.captchaPlaying = false;
|
||||
i.setState(i.state);
|
||||
WebSocketService.Instance.send(wsClient.getCaptcha());
|
||||
}
|
||||
|
||||
handlePasswordReset(i: Login, event: any) {
|
||||
event.preventDefault();
|
||||
let resetForm: PasswordReset = {
|
||||
|
@ -421,37 +179,12 @@ export class Login extends Component<any, State> {
|
|||
WebSocketService.Instance.send(wsClient.passwordReset(resetForm));
|
||||
}
|
||||
|
||||
handleCaptchaPlay(i: Login) {
|
||||
// This was a bad bug, it should only build the new audio on a new file.
|
||||
// Replays would stop prematurely if this was rebuilt every time.
|
||||
if (i.audio == null) {
|
||||
let base64 = `data:audio/wav;base64,${i.state.captcha.ok.wav}`;
|
||||
i.audio = new Audio(base64);
|
||||
}
|
||||
|
||||
i.audio.play();
|
||||
|
||||
i.state.captchaPlaying = true;
|
||||
i.setState(i.state);
|
||||
|
||||
i.audio.addEventListener("ended", () => {
|
||||
i.audio.currentTime = 0;
|
||||
i.state.captchaPlaying = false;
|
||||
i.setState(i.state);
|
||||
});
|
||||
}
|
||||
|
||||
captchaPngSrc() {
|
||||
return `data:image/png;base64,${this.state.captcha.ok.png}`;
|
||||
}
|
||||
|
||||
parseMessage(msg: any) {
|
||||
let op = wsUserOp(msg);
|
||||
console.log(msg);
|
||||
if (msg.error) {
|
||||
toast(i18n.t(msg.error), "danger");
|
||||
this.state = this.emptyState;
|
||||
this.state.registerForm.captcha_answer = undefined;
|
||||
// Refetch another captcha
|
||||
WebSocketService.Instance.send(wsClient.getCaptcha());
|
||||
this.setState(this.state);
|
||||
|
@ -469,24 +202,6 @@ export class Login extends Component<any, State> {
|
|||
);
|
||||
toast(i18n.t("logged_in"));
|
||||
this.props.history.push("/");
|
||||
} else if (op == UserOperation.Register) {
|
||||
let data = wsJsonToRes<LoginResponse>(msg).data;
|
||||
this.state = this.emptyState;
|
||||
this.setState(this.state);
|
||||
UserService.Instance.login(data);
|
||||
WebSocketService.Instance.send(
|
||||
wsClient.userJoin({
|
||||
auth: authField(),
|
||||
})
|
||||
);
|
||||
this.props.history.push("/communities");
|
||||
} else if (op == UserOperation.GetCaptcha) {
|
||||
let data = wsJsonToRes<GetCaptchaResponse>(msg).data;
|
||||
if (data.ok) {
|
||||
this.state.captcha = data;
|
||||
this.state.registerForm.captcha_uuid = data.ok.uuid;
|
||||
this.setState(this.state);
|
||||
}
|
||||
} else if (op == UserOperation.PasswordReset) {
|
||||
toast(i18n.t("reset_password_mail_sent"));
|
||||
} else if (op == UserOperation.GetSite) {
|
||||
|
|
444
src/shared/components/home/signup.tsx
Normal file
444
src/shared/components/home/signup.tsx
Normal file
|
@ -0,0 +1,444 @@
|
|||
import { Component, linkEvent } from "inferno";
|
||||
import { T } from "inferno-i18next-dess";
|
||||
import {
|
||||
GetCaptchaResponse,
|
||||
GetSiteResponse,
|
||||
LoginResponse,
|
||||
Register,
|
||||
SiteView,
|
||||
UserOperation,
|
||||
} from "lemmy-js-client";
|
||||
import { Subscription } from "rxjs";
|
||||
import { i18n } from "../../i18next";
|
||||
import { Options, passwordStrength } from "check-password-strength";
|
||||
import { UserService, WebSocketService } from "../../services";
|
||||
import {
|
||||
authField,
|
||||
isBrowser,
|
||||
joinLemmyUrl,
|
||||
setIsoData,
|
||||
toast,
|
||||
validEmail,
|
||||
wsClient,
|
||||
wsJsonToRes,
|
||||
wsSubscribe,
|
||||
wsUserOp,
|
||||
} from "../../utils";
|
||||
import { HtmlTags } from "../common/html-tags";
|
||||
import { Icon, Spinner } from "../common/icon";
|
||||
import {I18nKeys} from "i18next";
|
||||
|
||||
const passwordStrengthOptions: Options<string> = [
|
||||
{
|
||||
id: 0,
|
||||
value: "too_weak",
|
||||
minDiversity: 0,
|
||||
minLength: 0,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
value: "weak",
|
||||
minDiversity: 2,
|
||||
minLength: 10,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
value: "medium",
|
||||
minDiversity: 3,
|
||||
minLength: 12,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
value: "strong",
|
||||
minDiversity: 4,
|
||||
minLength: 14,
|
||||
},
|
||||
];
|
||||
|
||||
interface State {
|
||||
registerForm: Register;
|
||||
registerLoading: boolean;
|
||||
captcha: GetCaptchaResponse;
|
||||
captchaPlaying: boolean;
|
||||
site_view: SiteView;
|
||||
}
|
||||
|
||||
export class Signup extends Component<any, State> {
|
||||
private isoData = setIsoData(this.context);
|
||||
private subscription: Subscription;
|
||||
private audio: HTMLAudioElement;
|
||||
|
||||
emptyState: State = {
|
||||
registerForm: {
|
||||
username: undefined,
|
||||
password: undefined,
|
||||
password_verify: undefined,
|
||||
show_nsfw: false,
|
||||
captcha_uuid: undefined,
|
||||
captcha_answer: undefined,
|
||||
},
|
||||
registerLoading: false,
|
||||
captcha: undefined,
|
||||
captchaPlaying: false,
|
||||
site_view: this.isoData.site_res.site_view,
|
||||
};
|
||||
|
||||
constructor(props: any, context: any) {
|
||||
super(props, context);
|
||||
|
||||
this.state = this.emptyState;
|
||||
|
||||
this.parseMessage = this.parseMessage.bind(this);
|
||||
this.subscription = wsSubscribe(this.parseMessage);
|
||||
|
||||
if (isBrowser()) {
|
||||
WebSocketService.Instance.send(wsClient.getCaptcha());
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (isBrowser()) {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
get documentTitle(): string {
|
||||
return `${i18n.t("login")} - ${this.state.site_view.site.name}`;
|
||||
}
|
||||
|
||||
get isLemmyMl(): boolean {
|
||||
return isBrowser() && window.location.hostname == "lemmy.ml";
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div class="container">
|
||||
<HtmlTags
|
||||
title={this.documentTitle}
|
||||
path={this.context.router.route.match.url}
|
||||
/>
|
||||
<div class="row">
|
||||
<div class="col-12 col-lg-6 offset-lg-3">{this.registerForm()}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
registerForm() {
|
||||
return (
|
||||
<form onSubmit={linkEvent(this, this.handleRegisterSubmit)}>
|
||||
<h5>{i18n.t("sign_up")}</h5>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-2 col-form-label" htmlFor="register-username">
|
||||
{i18n.t("username")}
|
||||
</label>
|
||||
|
||||
<div class="col-sm-10">
|
||||
<input
|
||||
type="text"
|
||||
id="register-username"
|
||||
class="form-control"
|
||||
value={this.state.registerForm.username}
|
||||
onInput={linkEvent(this, this.handleRegisterUsernameChange)}
|
||||
required
|
||||
minLength={3}
|
||||
pattern="[a-zA-Z0-9_]+"
|
||||
title={i18n.t("community_reqs")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-2 col-form-label" htmlFor="register-email">
|
||||
{i18n.t("email")}
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<input
|
||||
type="email"
|
||||
id="register-email"
|
||||
class="form-control"
|
||||
placeholder={i18n.t("optional")}
|
||||
value={this.state.registerForm.email}
|
||||
autoComplete="email"
|
||||
onInput={linkEvent(this, this.handleRegisterEmailChange)}
|
||||
minLength={3}
|
||||
/>
|
||||
{!validEmail(this.state.registerForm.email) && (
|
||||
<div class="mt-2 mb-0 alert alert-light" role="alert">
|
||||
<Icon icon="alert-triangle" classes="icon-inline mr-2" />
|
||||
{i18n.t("no_password_reset")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-2 col-form-label" htmlFor="register-password">
|
||||
{i18n.t("password")}
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<input
|
||||
type="password"
|
||||
id="register-password"
|
||||
value={this.state.registerForm.password}
|
||||
autoComplete="new-password"
|
||||
onInput={linkEvent(this, this.handleRegisterPasswordChange)}
|
||||
minLength={10}
|
||||
maxLength={60}
|
||||
class="form-control"
|
||||
required
|
||||
/>
|
||||
{this.state.registerForm.password && (
|
||||
<div class={this.passwordColorClass}>
|
||||
{i18n.t(this.passwordStrength as I18nKeys)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label
|
||||
class="col-sm-2 col-form-label"
|
||||
htmlFor="register-verify-password"
|
||||
>
|
||||
{i18n.t("verify_password")}
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<input
|
||||
type="password"
|
||||
id="register-verify-password"
|
||||
value={this.state.registerForm.password_verify}
|
||||
autoComplete="new-password"
|
||||
onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)}
|
||||
maxLength={60}
|
||||
class="form-control"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{this.state.captcha && (
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-2" htmlFor="register-captcha">
|
||||
<span class="mr-2">{i18n.t("enter_code")}</span>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
onClick={linkEvent(this, this.handleRegenCaptcha)}
|
||||
aria-label={i18n.t("captcha")}
|
||||
>
|
||||
<Icon icon="refresh-cw" classes="icon-refresh-cw" />
|
||||
</button>
|
||||
</label>
|
||||
{this.showCaptcha()}
|
||||
<div class="col-sm-6">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="register-captcha"
|
||||
value={this.state.registerForm.captcha_answer}
|
||||
onInput={linkEvent(
|
||||
this,
|
||||
this.handleRegisterCaptchaAnswerChange
|
||||
)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{this.state.site_view.site.enable_nsfw && (
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-10">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
id="register-show-nsfw"
|
||||
type="checkbox"
|
||||
checked={this.state.registerForm.show_nsfw}
|
||||
onChange={linkEvent(this, this.handleRegisterShowNsfwChange)}
|
||||
/>
|
||||
<label class="form-check-label" htmlFor="register-show-nsfw">
|
||||
{i18n.t("show_nsfw")}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{this.isLemmyMl && (
|
||||
<div class="mt-2 mb-0 alert alert-light" role="alert">
|
||||
<T i18nKey="lemmy_ml_registration_message">
|
||||
#<a href={joinLemmyUrl}>#</a>
|
||||
</T>
|
||||
</div>
|
||||
)}
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-10">
|
||||
<button type="submit" class="btn btn-secondary">
|
||||
{this.state.registerLoading ? <Spinner /> : i18n.t("sign_up")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
showCaptcha() {
|
||||
return (
|
||||
<div class="col-sm-4">
|
||||
{this.state.captcha.ok && (
|
||||
<>
|
||||
<img
|
||||
class="rounded-top img-fluid"
|
||||
src={this.captchaPngSrc()}
|
||||
style="border-bottom-right-radius: 0; border-bottom-left-radius: 0;"
|
||||
alt={i18n.t("captcha")}
|
||||
/>
|
||||
{this.state.captcha.ok.wav && (
|
||||
<button
|
||||
class="rounded-bottom btn btn-sm btn-secondary btn-block"
|
||||
style="border-top-right-radius: 0; border-top-left-radius: 0;"
|
||||
title={i18n.t("play_captcha_audio")}
|
||||
onClick={linkEvent(this, this.handleCaptchaPlay)}
|
||||
type="button"
|
||||
disabled={this.state.captchaPlaying}
|
||||
>
|
||||
<Icon icon="play" classes="icon-play" />
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
get passwordStrength() {
|
||||
return passwordStrength(
|
||||
this.state.registerForm.password,
|
||||
passwordStrengthOptions
|
||||
).value;
|
||||
}
|
||||
|
||||
get passwordColorClass(): string {
|
||||
let strength = this.passwordStrength;
|
||||
|
||||
if (["weak", "medium"].includes(strength)) {
|
||||
return "text-warning";
|
||||
} else if (strength == "strong") {
|
||||
return "text-success";
|
||||
} else {
|
||||
return "text-danger";
|
||||
}
|
||||
}
|
||||
|
||||
handleRegisterSubmit(i: Signup, event: any) {
|
||||
event.preventDefault();
|
||||
i.state.registerLoading = true;
|
||||
i.setState(i.state);
|
||||
WebSocketService.Instance.send(wsClient.register(i.state.registerForm));
|
||||
}
|
||||
|
||||
handleRegisterUsernameChange(i: Signup, event: any) {
|
||||
i.state.registerForm.username = event.target.value;
|
||||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleRegisterEmailChange(i: Signup, event: any) {
|
||||
i.state.registerForm.email = event.target.value;
|
||||
if (i.state.registerForm.email == "") {
|
||||
i.state.registerForm.email = undefined;
|
||||
}
|
||||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleRegisterPasswordChange(i: Signup, event: any) {
|
||||
i.state.registerForm.password = event.target.value;
|
||||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleRegisterPasswordVerifyChange(i: Signup, event: any) {
|
||||
i.state.registerForm.password_verify = event.target.value;
|
||||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleRegisterShowNsfwChange(i: Signup, event: any) {
|
||||
i.state.registerForm.show_nsfw = event.target.checked;
|
||||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleRegisterCaptchaAnswerChange(i: Signup, event: any) {
|
||||
i.state.registerForm.captcha_answer = event.target.value;
|
||||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleRegenCaptcha(i: Signup) {
|
||||
i.audio = null;
|
||||
i.state.captchaPlaying = false;
|
||||
i.setState(i.state);
|
||||
WebSocketService.Instance.send(wsClient.getCaptcha());
|
||||
}
|
||||
|
||||
handleCaptchaPlay(i: Signup) {
|
||||
// This was a bad bug, it should only build the new audio on a new file.
|
||||
// Replays would stop prematurely if this was rebuilt every time.
|
||||
if (i.audio == null) {
|
||||
let base64 = `data:audio/wav;base64,${i.state.captcha.ok.wav}`;
|
||||
i.audio = new Audio(base64);
|
||||
}
|
||||
|
||||
i.audio.play();
|
||||
|
||||
i.state.captchaPlaying = true;
|
||||
i.setState(i.state);
|
||||
|
||||
i.audio.addEventListener("ended", () => {
|
||||
i.audio.currentTime = 0;
|
||||
i.state.captchaPlaying = false;
|
||||
i.setState(i.state);
|
||||
});
|
||||
}
|
||||
|
||||
captchaPngSrc() {
|
||||
return `data:image/png;base64,${this.state.captcha.ok.png}`;
|
||||
}
|
||||
|
||||
parseMessage(msg: any) {
|
||||
let op = wsUserOp(msg);
|
||||
console.log(msg);
|
||||
if (msg.error) {
|
||||
toast(i18n.t(msg.error), "danger");
|
||||
this.state = this.emptyState;
|
||||
this.state.registerForm.captcha_answer = undefined;
|
||||
// Refetch another captcha
|
||||
WebSocketService.Instance.send(wsClient.getCaptcha());
|
||||
this.setState(this.state);
|
||||
return;
|
||||
} else {
|
||||
if (op == UserOperation.Register) {
|
||||
let data = wsJsonToRes<LoginResponse>(msg).data;
|
||||
this.state = this.emptyState;
|
||||
this.setState(this.state);
|
||||
UserService.Instance.login(data);
|
||||
WebSocketService.Instance.send(
|
||||
wsClient.userJoin({
|
||||
auth: authField(),
|
||||
})
|
||||
);
|
||||
this.props.history.push("/communities");
|
||||
} else if (op == UserOperation.GetCaptcha) {
|
||||
let data = wsJsonToRes<GetCaptchaResponse>(msg).data;
|
||||
if (data.ok) {
|
||||
this.state.captcha = data;
|
||||
this.state.registerForm.captcha_uuid = data.ok.uuid;
|
||||
this.setState(this.state);
|
||||
}
|
||||
} else if (op == UserOperation.PasswordReset) {
|
||||
toast(i18n.t("reset_password_mail_sent"));
|
||||
} else if (op == UserOperation.GetSite) {
|
||||
let data = wsJsonToRes<GetSiteResponse>(msg).data;
|
||||
this.state.site_view = data.site_view;
|
||||
this.setState(this.state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -28,6 +28,7 @@ import {
|
|||
fetchLimit,
|
||||
getUsernameFromProps,
|
||||
mdToHtml,
|
||||
numToSI,
|
||||
previewLines,
|
||||
restoreScrollPosition,
|
||||
routeSortTypeToEnum,
|
||||
|
@ -96,6 +97,7 @@ export class Profile extends Component<any, ProfileState> {
|
|||
|
||||
this.state = this.emptyState;
|
||||
this.handleSortChange = this.handleSortChange.bind(this);
|
||||
this.handlePageChange = this.handlePageChange.bind(this);
|
||||
|
||||
this.parseMessage = this.parseMessage.bind(this);
|
||||
this.subscription = wsSubscribe(this.parseMessage);
|
||||
|
@ -453,11 +455,15 @@ export class Profile extends Component<any, ProfileState> {
|
|||
<div>
|
||||
<ul class="list-inline mb-2">
|
||||
<li className="list-inline-item badge badge-light">
|
||||
{i18n.t("number_of_posts", { count: pv.counts.post_count })}
|
||||
{i18n.t("number_of_posts", {
|
||||
count: pv.counts.post_count,
|
||||
formattedCount: numToSI(pv.counts.post_count),
|
||||
})}
|
||||
</li>
|
||||
<li className="list-inline-item badge badge-light">
|
||||
{i18n.t("number_of_comments", {
|
||||
count: pv.counts.comment_count,
|
||||
formattedCount: numToSI(pv.counts.comment_count),
|
||||
})}
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
@ -227,10 +227,10 @@ export class Settings extends Component<any, SettingsState> {
|
|||
<h5>{i18n.t("change_password")}</h5>
|
||||
<form onSubmit={linkEvent(this, this.handleChangePasswordSubmit)}>
|
||||
<div class="form-group row">
|
||||
<label class="col-lg-5 col-form-label" htmlFor="user-password">
|
||||
<label class="col-sm-5 col-form-label" htmlFor="user-password">
|
||||
{i18n.t("new_password")}
|
||||
</label>
|
||||
<div class="col-lg-7">
|
||||
<div class="col-sm-7">
|
||||
<input
|
||||
type="password"
|
||||
id="user-password"
|
||||
|
@ -244,12 +244,12 @@ export class Settings extends Component<any, SettingsState> {
|
|||
</div>
|
||||
<div class="form-group row">
|
||||
<label
|
||||
class="col-lg-5 col-form-label"
|
||||
class="col-sm-5 col-form-label"
|
||||
htmlFor="user-verify-password"
|
||||
>
|
||||
{i18n.t("verify_password")}
|
||||
</label>
|
||||
<div class="col-lg-7">
|
||||
<div class="col-sm-7">
|
||||
<input
|
||||
type="password"
|
||||
id="user-verify-password"
|
||||
|
@ -262,10 +262,10 @@ export class Settings extends Component<any, SettingsState> {
|
|||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-lg-5 col-form-label" htmlFor="user-old-password">
|
||||
<label class="col-sm-5 col-form-label" htmlFor="user-old-password">
|
||||
{i18n.t("old_password")}
|
||||
</label>
|
||||
<div class="col-lg-7">
|
||||
<div class="col-sm-7">
|
||||
<input
|
||||
type="password"
|
||||
id="user-old-password"
|
||||
|
@ -417,10 +417,10 @@ export class Settings extends Component<any, SettingsState> {
|
|||
<h5>{i18n.t("settings")}</h5>
|
||||
<form onSubmit={linkEvent(this, this.handleSaveSettingsSubmit)}>
|
||||
<div class="form-group row">
|
||||
<label class="col-lg-5 col-form-label" htmlFor="display-name">
|
||||
<label class="col-sm-5 col-form-label" htmlFor="display-name">
|
||||
{i18n.t("display_name")}
|
||||
</label>
|
||||
<div class="col-lg-7">
|
||||
<div class="col-sm-7">
|
||||
<input
|
||||
id="display-name"
|
||||
type="text"
|
||||
|
@ -434,10 +434,10 @@ export class Settings extends Component<any, SettingsState> {
|
|||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-lg-3 col-form-label" htmlFor="user-bio">
|
||||
<label class="col-sm-3 col-form-label" htmlFor="user-bio">
|
||||
{i18n.t("bio")}
|
||||
</label>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-sm-9">
|
||||
<MarkdownTextArea
|
||||
initialContent={this.state.saveUserSettingsForm.bio}
|
||||
onContentChange={this.handleBioChange}
|
||||
|
@ -447,10 +447,10 @@ export class Settings extends Component<any, SettingsState> {
|
|||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-lg-3 col-form-label" htmlFor="user-email">
|
||||
<label class="col-sm-3 col-form-label" htmlFor="user-email">
|
||||
{i18n.t("email")}
|
||||
</label>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-sm-9">
|
||||
<input
|
||||
type="email"
|
||||
id="user-email"
|
||||
|
@ -463,12 +463,12 @@ export class Settings extends Component<any, SettingsState> {
|
|||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-lg-5 col-form-label" htmlFor="matrix-user-id">
|
||||
<label class="col-sm-5 col-form-label" htmlFor="matrix-user-id">
|
||||
<a href={elementUrl} rel="noopener">
|
||||
{i18n.t("matrix_user_id")}
|
||||
</a>
|
||||
</label>
|
||||
<div class="col-lg-7">
|
||||
<div class="col-sm-7">
|
||||
<input
|
||||
id="matrix-user-id"
|
||||
type="text"
|
||||
|
@ -480,90 +480,102 @@ export class Settings extends Component<any, SettingsState> {
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>{i18n.t("avatar")}</label>
|
||||
<ImageUploadForm
|
||||
uploadTitle={i18n.t("upload_avatar")}
|
||||
imageSrc={this.state.saveUserSettingsForm.avatar}
|
||||
onUpload={this.handleAvatarUpload}
|
||||
onRemove={this.handleAvatarRemove}
|
||||
rounded
|
||||
/>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3">{i18n.t("avatar")}</label>
|
||||
<div class="col-sm-9">
|
||||
<ImageUploadForm
|
||||
uploadTitle={i18n.t("upload_avatar")}
|
||||
imageSrc={this.state.saveUserSettingsForm.avatar}
|
||||
onUpload={this.handleAvatarUpload}
|
||||
onRemove={this.handleAvatarRemove}
|
||||
rounded
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>{i18n.t("banner")}</label>
|
||||
<ImageUploadForm
|
||||
uploadTitle={i18n.t("upload_banner")}
|
||||
imageSrc={this.state.saveUserSettingsForm.banner}
|
||||
onUpload={this.handleBannerUpload}
|
||||
onRemove={this.handleBannerRemove}
|
||||
/>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3">{i18n.t("banner")}</label>
|
||||
<div class="col-sm-9">
|
||||
<ImageUploadForm
|
||||
uploadTitle={i18n.t("upload_banner")}
|
||||
imageSrc={this.state.saveUserSettingsForm.banner}
|
||||
onUpload={this.handleBannerUpload}
|
||||
onRemove={this.handleBannerRemove}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label htmlFor="user-language">{i18n.t("language")}</label>
|
||||
<select
|
||||
id="user-language"
|
||||
value={this.state.saveUserSettingsForm.lang}
|
||||
onChange={linkEvent(this, this.handleLangChange)}
|
||||
class="ml-2 custom-select w-auto"
|
||||
>
|
||||
<option disabled aria-hidden="true">
|
||||
{i18n.t("language")}
|
||||
</option>
|
||||
<option value="browser">{i18n.t("browser_default")}</option>
|
||||
<option disabled aria-hidden="true">
|
||||
──
|
||||
</option>
|
||||
{languages.sort().map(lang => (
|
||||
<option value={lang.code}>
|
||||
{ISO6391.getNativeName(lang.code) || lang.code}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3" htmlFor="user-language">
|
||||
{i18n.t("language")}
|
||||
</label>
|
||||
<div class="col-sm-9">
|
||||
<select
|
||||
id="user-language"
|
||||
value={this.state.saveUserSettingsForm.lang}
|
||||
onChange={linkEvent(this, this.handleLangChange)}
|
||||
class="custom-select w-auto"
|
||||
>
|
||||
<option disabled aria-hidden="true">
|
||||
{i18n.t("language")}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<option value="browser">{i18n.t("browser_default")}</option>
|
||||
<option disabled aria-hidden="true">
|
||||
──
|
||||
</option>
|
||||
{languages.sort().map(lang => (
|
||||
<option value={lang.code}>
|
||||
{ISO6391.getNativeName(lang.code) || lang.code}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label htmlFor="user-theme">{i18n.t("theme")}</label>
|
||||
<select
|
||||
id="user-theme"
|
||||
value={this.state.saveUserSettingsForm.theme}
|
||||
onChange={linkEvent(this, this.handleThemeChange)}
|
||||
class="ml-2 custom-select w-auto"
|
||||
>
|
||||
<option disabled aria-hidden="true">
|
||||
{i18n.t("theme")}
|
||||
</option>
|
||||
<option value="browser">{i18n.t("browser_default")}</option>
|
||||
{themes.map(theme => (
|
||||
<option value={theme}>{theme}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<form className="form-group">
|
||||
<label>
|
||||
<div class="mr-2">{i18n.t("type")}</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3" htmlFor="user-theme">
|
||||
{i18n.t("theme")}
|
||||
</label>
|
||||
<ListingTypeSelect
|
||||
type_={
|
||||
Object.values(ListingType)[
|
||||
this.state.saveUserSettingsForm.default_listing_type
|
||||
]
|
||||
}
|
||||
showLocal={showLocal(this.isoData)}
|
||||
onChange={this.handleListingTypeChange}
|
||||
/>
|
||||
<div class="col-sm-9">
|
||||
<select
|
||||
id="user-theme"
|
||||
value={this.state.saveUserSettingsForm.theme}
|
||||
onChange={linkEvent(this, this.handleThemeChange)}
|
||||
class="custom-select w-auto"
|
||||
>
|
||||
<option disabled aria-hidden="true">
|
||||
{i18n.t("theme")}
|
||||
</option>
|
||||
<option value="browser">{i18n.t("browser_default")}</option>
|
||||
{themes.map(theme => (
|
||||
<option value={theme}>{theme}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<form className="form-group row">
|
||||
<label class="col-sm-3">{i18n.t("type")}</label>
|
||||
<div class="col-sm-9">
|
||||
<ListingTypeSelect
|
||||
type_={
|
||||
Object.values(ListingType)[
|
||||
this.state.saveUserSettingsForm.default_listing_type
|
||||
]
|
||||
}
|
||||
showLocal={showLocal(this.isoData)}
|
||||
onChange={this.handleListingTypeChange}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
<form className="form-group">
|
||||
<label>
|
||||
<div class="mr-2">{i18n.t("sort_type")}</div>
|
||||
</label>
|
||||
<SortSelect
|
||||
sort={
|
||||
Object.values(SortType)[
|
||||
this.state.saveUserSettingsForm.default_sort_type
|
||||
]
|
||||
}
|
||||
onChange={this.handleSortTypeChange}
|
||||
/>
|
||||
<form className="form-group row">
|
||||
<label class="col-sm-3">{i18n.t("sort_type")}</label>
|
||||
<div class="col-sm-9">
|
||||
<SortSelect
|
||||
sort={
|
||||
Object.values(SortType)[
|
||||
this.state.saveUserSettingsForm.default_sort_type
|
||||
]
|
||||
}
|
||||
onChange={this.handleSortTypeChange}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
{this.state.siteRes.site_view.site.enable_nsfw && (
|
||||
<div class="form-group">
|
||||
|
@ -1023,11 +1035,6 @@ export class Settings extends Component<any, SettingsState> {
|
|||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleLogoutClick(i: Settings) {
|
||||
UserService.Instance.logout();
|
||||
i.context.router.history.push("/");
|
||||
}
|
||||
|
||||
handleDeleteAccount(i: Settings, event: any) {
|
||||
event.preventDefault();
|
||||
i.state.deleteAccountLoading = true;
|
||||
|
@ -1107,6 +1114,7 @@ export class Settings extends Component<any, SettingsState> {
|
|||
});
|
||||
UserService.Instance.logout();
|
||||
window.location.href = "/";
|
||||
location.reload();
|
||||
} else if (op == UserOperation.BlockPerson) {
|
||||
let data = wsJsonToRes<BlockPersonResponse>(msg).data;
|
||||
this.setState({ personBlocks: updatePersonBlock(data) });
|
||||
|
|
|
@ -310,18 +310,18 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
)}
|
||||
{this.props.enableNsfw && (
|
||||
<div class="form-group row">
|
||||
<legend class="col-form-label col-sm-2 pt-0">
|
||||
{i18n.t("nsfw")}
|
||||
</legend>
|
||||
<div class="col-sm-10">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
class="form-check-input position-static"
|
||||
id="post-nsfw"
|
||||
type="checkbox"
|
||||
checked={this.state.postForm.nsfw}
|
||||
onChange={linkEvent(this, this.handlePostNsfwChange)}
|
||||
/>
|
||||
<label class="form-check-label" htmlFor="post-nsfw">
|
||||
{i18n.t("nsfw")}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -32,6 +32,7 @@ import {
|
|||
isVideo,
|
||||
md,
|
||||
mdToHtml,
|
||||
numToSI,
|
||||
previewLines,
|
||||
setupTippy,
|
||||
showScores,
|
||||
|
@ -202,16 +203,16 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
|
||||
if (isImage(post.url)) {
|
||||
return (
|
||||
<div
|
||||
class="float-right text-body pointer d-inline-block position-relative mb-2"
|
||||
<a
|
||||
href={this.getImageSrc()}
|
||||
class="float-right text-body d-inline-block position-relative mb-2"
|
||||
data-tippy-content={i18n.t("expand_here")}
|
||||
onClick={linkEvent(this, this.handleImageExpandClick)}
|
||||
role="button"
|
||||
aria-label={i18n.t("expand_here")}
|
||||
>
|
||||
{this.imgThumb(this.getImageSrc())}
|
||||
<Icon icon="image" classes="mini-overlay" />
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
} else if (post.thumbnail_url) {
|
||||
return (
|
||||
|
@ -356,7 +357,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
class={`unselectable pointer font-weight-bold text-muted px-1`}
|
||||
data-tippy-content={this.pointsTippy}
|
||||
>
|
||||
{this.state.score}
|
||||
{numToSI(this.state.score)}
|
||||
</div>
|
||||
) : (
|
||||
<div class="p-1"></div>
|
||||
|
@ -418,12 +419,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
<Icon icon="minus-square" classes="icon-inline" />
|
||||
</button>
|
||||
<div>
|
||||
<button
|
||||
<a
|
||||
href={this.getImageSrc()}
|
||||
class="btn btn-link d-inline-block"
|
||||
onClick={linkEvent(this, this.handleImageExpandClick)}
|
||||
>
|
||||
<PictrsImage src={this.getImageSrc()} />
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</span>
|
||||
))}
|
||||
|
@ -475,12 +477,14 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
className="text-muted small"
|
||||
title={i18n.t("number_of_comments", {
|
||||
count: post_view.counts.comments,
|
||||
formattedCount: post_view.counts.comments,
|
||||
})}
|
||||
to={`/post/${post_view.post.id}?scrollToComments=true`}
|
||||
>
|
||||
<Icon icon="message-square" classes="icon-inline mr-1" />
|
||||
{i18n.t("number_of_comments", {
|
||||
count: post_view.counts.comments,
|
||||
formattedCount: numToSI(post_view.counts.comments),
|
||||
})}
|
||||
</Link>
|
||||
</button>
|
||||
|
@ -494,7 +498,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
>
|
||||
<small>
|
||||
<Icon icon="arrow-down1" classes="icon-inline mr-1" />
|
||||
<span>{this.state.downvotes}</span>
|
||||
<span>{numToSI(this.state.downvotes)}</span>
|
||||
</small>
|
||||
</button>
|
||||
)}
|
||||
|
@ -532,7 +536,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
aria-label={i18n.t("upvote")}
|
||||
>
|
||||
<Icon icon="arrow-up1" classes="icon-inline small mr-2" />
|
||||
{this.state.upvotes}
|
||||
{numToSI(this.state.upvotes)}
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
|
@ -557,7 +561,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
>
|
||||
<Icon icon="arrow-down1" classes="icon-inline small mr-2" />
|
||||
{this.state.downvotes !== 0 && (
|
||||
<span>{this.state.downvotes}</span>
|
||||
<span>{numToSI(this.state.downvotes)}</span>
|
||||
)}
|
||||
</button>
|
||||
) : (
|
||||
|
@ -1539,7 +1543,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleImageExpandClick(i: PostListing) {
|
||||
handleImageExpandClick(i: PostListing, event: any) {
|
||||
event.preventDefault();
|
||||
i.state.imageExpanded = !i.state.imageExpanded;
|
||||
i.setState(i.state);
|
||||
}
|
||||
|
@ -1571,14 +1576,17 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
get pointsTippy(): string {
|
||||
let points = i18n.t("number_of_points", {
|
||||
count: this.state.score,
|
||||
formattedCount: this.state.score,
|
||||
});
|
||||
|
||||
let upvotes = i18n.t("number_of_upvotes", {
|
||||
count: this.state.upvotes,
|
||||
formattedCount: this.state.upvotes,
|
||||
});
|
||||
|
||||
let downvotes = i18n.t("number_of_downvotes", {
|
||||
count: this.state.downvotes,
|
||||
formattedCount: this.state.downvotes,
|
||||
});
|
||||
|
||||
return `${points} • ${upvotes} • ${downvotes}`;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Component } from "inferno";
|
||||
import { T } from "inferno-i18next";
|
||||
import { T } from "inferno-i18next-dess";
|
||||
import { Link } from "inferno-router";
|
||||
import { PostView } from "lemmy-js-client";
|
||||
import { i18n } from "../../i18next";
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Component, linkEvent } from "inferno";
|
||||
import { T } from "inferno-i18next";
|
||||
import { T } from "inferno-i18next-dess";
|
||||
import { Prompt } from "inferno-router";
|
||||
import {
|
||||
CreatePrivateMessage,
|
||||
|
|
|
@ -38,6 +38,7 @@ import {
|
|||
fetchLimit,
|
||||
fetchUsers,
|
||||
isBrowser,
|
||||
numToSI,
|
||||
personSelectName,
|
||||
personToChoice,
|
||||
restoreScrollPosition,
|
||||
|
@ -649,6 +650,7 @@ export class Search extends Component<any, SearchState> {
|
|||
<span>{` -
|
||||
${i18n.t("number_of_subscribers", {
|
||||
count: community_view.counts.subscribers,
|
||||
formattedCount: numToSI(community_view.counts.subscribers),
|
||||
})}
|
||||
`}</span>
|
||||
</>
|
||||
|
@ -662,6 +664,7 @@ export class Search extends Component<any, SearchState> {
|
|||
</span>,
|
||||
<span>{` - ${i18n.t("number_of_comments", {
|
||||
count: person_view.counts.comment_count,
|
||||
formattedCount: numToSI(person_view.counts.comment_count),
|
||||
})}`}</span>,
|
||||
];
|
||||
}
|
||||
|
|
|
@ -37,9 +37,11 @@ export const httpBaseInternal = `http://${host}`; // Don't use secure here
|
|||
export const httpBase = `http${secure}://${host}`;
|
||||
export const wsUri = `ws${secure}://${wsHost}/api/v3/ws`;
|
||||
export const pictrsUri = `${httpBase}/pictrs/image`;
|
||||
export const isHttps = secure.endsWith("s");
|
||||
|
||||
console.log(`httpbase: ${httpBase}`);
|
||||
console.log(`wsUri: ${wsUri}`);
|
||||
console.log(`isHttps: ${isHttps}`);
|
||||
|
||||
// This is for html tags, don't include port
|
||||
const httpExternalUri = `http${secure}://${externalHost.split(":")[0]}`;
|
||||
|
|
|
@ -8,6 +8,7 @@ import { Instances } from "./components/home/instances";
|
|||
import { Login } from "./components/home/login";
|
||||
import { PasswordChange } from "./components/home/password_change";
|
||||
import { Setup } from "./components/home/setup";
|
||||
import { Signup } from "./components/home/signup";
|
||||
import { Modlog } from "./components/modlog";
|
||||
import { Inbox } from "./components/person/inbox";
|
||||
import { Profile } from "./components/person/profile";
|
||||
|
@ -38,6 +39,10 @@ export const routes: IRoutePropsWithFetch[] = [
|
|||
path: `/login`,
|
||||
component: Login,
|
||||
},
|
||||
{
|
||||
path: `/signup`,
|
||||
component: Signup,
|
||||
},
|
||||
{
|
||||
path: `/create_post`,
|
||||
component: CreatePost,
|
||||
|
|
|
@ -3,6 +3,7 @@ import IsomorphicCookie from "isomorphic-cookie";
|
|||
import jwt_decode from "jwt-decode";
|
||||
import { LoginResponse, MyUserInfo } from "lemmy-js-client";
|
||||
import { BehaviorSubject, Subject } from "rxjs";
|
||||
import { isHttps } from "../env";
|
||||
|
||||
interface Claims {
|
||||
sub: number;
|
||||
|
@ -31,17 +32,18 @@ export class UserService {
|
|||
public login(res: LoginResponse) {
|
||||
let expires = new Date();
|
||||
expires.setDate(expires.getDate() + 365);
|
||||
IsomorphicCookie.save("jwt", res.jwt, { expires, secure: false });
|
||||
IsomorphicCookie.save("jwt", res.jwt, { expires, secure: isHttps });
|
||||
console.log("jwt cookie set");
|
||||
this.setClaims(res.jwt);
|
||||
}
|
||||
|
||||
public logout() {
|
||||
IsomorphicCookie.remove("jwt", { secure: false });
|
||||
this.claims = undefined;
|
||||
this.myUserInfo = undefined;
|
||||
// setTheme();
|
||||
this.jwtSub.next("");
|
||||
IsomorphicCookie.remove("jwt"); // TODO is sometimes unreliable for some reason
|
||||
document.cookie = "jwt=; Max-Age=0; path=/; domain=" + location.host;
|
||||
console.log("Logged out.");
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import {
|
|||
} from "lemmy-js-client";
|
||||
import markdown_it from "markdown-it";
|
||||
import markdown_it_container from "markdown-it-container";
|
||||
import markdown_it_html5_embed from "markdown-it-html5-embed";
|
||||
import markdown_it_sub from "markdown-it-sub";
|
||||
import markdown_it_sup from "markdown-it-sup";
|
||||
import moment from "moment";
|
||||
|
@ -91,13 +92,13 @@ export const favIconPngUrl = "/static/assets/icons/apple-touch-icon.png";
|
|||
// export const defaultFavIcon = `${window.location.protocol}//${window.location.host}${favIconPngUrl}`;
|
||||
export const repoUrl = "https://github.com/LemmyNet";
|
||||
export const joinLemmyUrl = "https://join-lemmy.org";
|
||||
export const supportLemmyUrl = `${joinLemmyUrl}/support`;
|
||||
export const donateLemmyUrl = `${joinLemmyUrl}/donate`;
|
||||
export const docsUrl = `${joinLemmyUrl}/docs/en/index.html`;
|
||||
export const helpGuideUrl = `${joinLemmyUrl}/docs/en/about/guide.html`; // TODO find a way to redirect to the non-en folder
|
||||
export const markdownHelpUrl = `${helpGuideUrl}#markdown-guide`;
|
||||
export const sortingHelpUrl = `${helpGuideUrl}#sorting`;
|
||||
export const archiveUrl = "https://archive.is";
|
||||
export const elementUrl = "https://element.io/";
|
||||
export const elementUrl = "https://element.io";
|
||||
|
||||
export const postRefetchSeconds: number = 60 * 1000;
|
||||
export const fetchLimit = 20;
|
||||
|
@ -208,6 +209,16 @@ export const md = new markdown_it({
|
|||
})
|
||||
.use(markdown_it_sub)
|
||||
.use(markdown_it_sup)
|
||||
.use(markdown_it_html5_embed, {
|
||||
html5embed: {
|
||||
useImageSyntax: true, // Enables video/audio embed with ![]() syntax (default)
|
||||
attributes: {
|
||||
audio: 'controls preload="metadata"',
|
||||
video:
|
||||
'width="100%" max-height="100%" controls loop preload="metadata"',
|
||||
},
|
||||
},
|
||||
})
|
||||
.use(markdown_it_container, "spoiler", {
|
||||
validate: function (params: any) {
|
||||
return params.trim().match(/^spoiler\s+(.*)$/);
|
||||
|
@ -1431,3 +1442,14 @@ export function initializeSite(site: GetSiteResponse) {
|
|||
UserService.Instance.myUserInfo = site.my_user;
|
||||
i18n.changeLanguage(getLanguage());
|
||||
}
|
||||
|
||||
const SHORTNUM_SI_FORMAT = new Intl.NumberFormat("en-US", {
|
||||
maximumSignificantDigits: 3,
|
||||
//@ts-ignore
|
||||
notation: "compact",
|
||||
compactDisplay: "short",
|
||||
});
|
||||
|
||||
export function numToSI(value: number): string {
|
||||
return SHORTNUM_SI_FORMAT.format(value);
|
||||
}
|
||||
|
|
198
yarn.lock
198
yarn.lock
|
@ -1164,14 +1164,14 @@
|
|||
eslint-scope "^5.1.1"
|
||||
eslint-utils "^3.0.0"
|
||||
|
||||
"@typescript-eslint/parser@^4.28.3":
|
||||
version "4.29.2"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.29.2.tgz#1c7744f4c27aeb74610c955d3dce9250e95c370a"
|
||||
integrity sha512-WQ6BPf+lNuwteUuyk1jD/aHKqMQ9jrdCn7Gxt9vvBnzbpj7aWEf+aZsJ1zvTjx5zFxGCt000lsbD9tQPEL8u6g==
|
||||
"@typescript-eslint/parser@^4.31.1":
|
||||
version "4.31.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.31.1.tgz#8f9a2672033e6f6d33b1c0260eebdc0ddf539064"
|
||||
integrity sha512-dnVZDB6FhpIby6yVbHkwTKkn2ypjVIfAR9nh+kYsA/ZL0JlTsd22BiDjouotisY3Irmd3OW1qlk9EI5R8GrvRQ==
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager" "4.29.2"
|
||||
"@typescript-eslint/types" "4.29.2"
|
||||
"@typescript-eslint/typescript-estree" "4.29.2"
|
||||
"@typescript-eslint/scope-manager" "4.31.1"
|
||||
"@typescript-eslint/types" "4.31.1"
|
||||
"@typescript-eslint/typescript-estree" "4.31.1"
|
||||
debug "^4.3.1"
|
||||
|
||||
"@typescript-eslint/scope-manager@4.29.2":
|
||||
|
@ -1182,11 +1182,24 @@
|
|||
"@typescript-eslint/types" "4.29.2"
|
||||
"@typescript-eslint/visitor-keys" "4.29.2"
|
||||
|
||||
"@typescript-eslint/scope-manager@4.31.1":
|
||||
version "4.31.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.31.1.tgz#0c21e8501f608d6a25c842fcf59541ef4f1ab561"
|
||||
integrity sha512-N1Uhn6SqNtU2XpFSkD4oA+F0PfKdWHyr4bTX0xTj8NRx1314gBDRL1LUuZd5+L3oP+wo6hCbZpaa1in6SwMcVQ==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "4.31.1"
|
||||
"@typescript-eslint/visitor-keys" "4.31.1"
|
||||
|
||||
"@typescript-eslint/types@4.29.2":
|
||||
version "4.29.2"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.29.2.tgz#fc0489c6b89773f99109fb0aa0aaddff21f52fcd"
|
||||
integrity sha512-K6ApnEXId+WTGxqnda8z4LhNMa/pZmbTFkDxEBLQAbhLZL50DjeY0VIDCml/0Y3FlcbqXZrABqrcKxq+n0LwzQ==
|
||||
|
||||
"@typescript-eslint/types@4.31.1":
|
||||
version "4.31.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.31.1.tgz#5f255b695627a13401d2fdba5f7138bc79450d66"
|
||||
integrity sha512-kixltt51ZJGKENNW88IY5MYqTBA8FR0Md8QdGbJD2pKZ+D5IvxjTYDNtJPDxFBiXmka2aJsITdB1BtO1fsgmsQ==
|
||||
|
||||
"@typescript-eslint/typescript-estree@4.29.2":
|
||||
version "4.29.2"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.29.2.tgz#a0ea8b98b274adbb2577100ba545ddf8bf7dc219"
|
||||
|
@ -1200,6 +1213,19 @@
|
|||
semver "^7.3.5"
|
||||
tsutils "^3.21.0"
|
||||
|
||||
"@typescript-eslint/typescript-estree@4.31.1":
|
||||
version "4.31.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.31.1.tgz#4a04d5232cf1031232b7124a9c0310b577a62d17"
|
||||
integrity sha512-EGHkbsUvjFrvRnusk6yFGqrqMBTue5E5ROnS5puj3laGQPasVUgwhrxfcgkdHNFECHAewpvELE1Gjv0XO3mdWg==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "4.31.1"
|
||||
"@typescript-eslint/visitor-keys" "4.31.1"
|
||||
debug "^4.3.1"
|
||||
globby "^11.0.3"
|
||||
is-glob "^4.0.1"
|
||||
semver "^7.3.5"
|
||||
tsutils "^3.21.0"
|
||||
|
||||
"@typescript-eslint/visitor-keys@4.29.2":
|
||||
version "4.29.2"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.29.2.tgz#d2da7341f3519486f50655159f4e5ecdcb2cd1df"
|
||||
|
@ -1208,6 +1234,14 @@
|
|||
"@typescript-eslint/types" "4.29.2"
|
||||
eslint-visitor-keys "^2.0.0"
|
||||
|
||||
"@typescript-eslint/visitor-keys@4.31.1":
|
||||
version "4.31.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.31.1.tgz#f2e7a14c7f20c4ae07d7fc3c5878c4441a1da9cc"
|
||||
integrity sha512-PCncP8hEqKw6SOJY+3St4LVtoZpPPn+Zlpm7KW5xnviMhdqcsBty4Lsg4J/VECpJjw1CkROaZhH4B8M1OfnXTQ==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "4.31.1"
|
||||
eslint-visitor-keys "^2.0.0"
|
||||
|
||||
"@webassemblyjs/ast@1.11.1":
|
||||
version "1.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7"
|
||||
|
@ -2028,6 +2062,11 @@ chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1:
|
|||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
check-password-strength@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/check-password-strength/-/check-password-strength-2.0.3.tgz#fed038b1c457ac11a2999bd96f3185af34e88895"
|
||||
integrity sha512-UW3YgMUne9QuejgnNWjWwYi4QhWArVj+1OXqDR1NkEQcmMKKO74O3P5ZvXr9JZNbTBfcwlK3yurYCMuJsck83A==
|
||||
|
||||
choices.js@^9.0.1:
|
||||
version "9.0.1"
|
||||
resolved "https://registry.yarnpkg.com/choices.js/-/choices.js-9.0.1.tgz#745fb29af8670428fdc0bf1cc9dfaa404e9d0510"
|
||||
|
@ -2800,6 +2839,11 @@ enquirer@^2.3.5, enquirer@^2.3.6:
|
|||
dependencies:
|
||||
ansi-colors "^4.1.1"
|
||||
|
||||
entities@~1.1.1:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
|
||||
integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==
|
||||
|
||||
entities@~2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
|
||||
|
@ -3862,10 +3906,10 @@ husky@^7.0.1:
|
|||
resolved "https://registry.yarnpkg.com/husky/-/husky-7.0.1.tgz#579f4180b5da4520263e8713cc832942b48e1f1c"
|
||||
integrity sha512-gceRaITVZ+cJH9sNHqx5tFwbzlLCVxtVZcusME8JYQ8Edy5mpGDOqD8QBCdMhpyo9a+JXddnujQ4rpY2Ff9SJA==
|
||||
|
||||
i18next@^20.3.3:
|
||||
version "20.4.0"
|
||||
resolved "https://registry.yarnpkg.com/i18next/-/i18next-20.4.0.tgz#6897229a7898e23f3c4885f10315c978b594d3b9"
|
||||
integrity sha512-89iWWJudmaHJwzIdJ/1eu98GtsJnwBhOUWwlAre70itPMuTE/NTPtgVeaS1CGaB8Q3XrYBGpEqlq4jsScDx9kg==
|
||||
i18next@^20.6.1:
|
||||
version "20.6.1"
|
||||
resolved "https://registry.yarnpkg.com/i18next/-/i18next-20.6.1.tgz#535e5f6e5baeb685c7d25df70db63bf3cc0aa345"
|
||||
integrity sha512-yCMYTMEJ9ihCwEQQ3phLo7I/Pwycf8uAx+sRHwwk5U9Aui/IZYgQRyMqXafQOw5QQ7DM1Z+WyEXWIqSuJHhG2A==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.0"
|
||||
|
||||
|
@ -4015,6 +4059,13 @@ inferno-clone-vnode@^7.4.2:
|
|||
dependencies:
|
||||
inferno "7.4.8"
|
||||
|
||||
inferno-create-element@^7.4.10:
|
||||
version "7.4.10"
|
||||
resolved "https://registry.yarnpkg.com/inferno-create-element/-/inferno-create-element-7.4.10.tgz#a4d143c98aa141345fd9969b55b56f57336e0329"
|
||||
integrity sha512-Gvq0FHL7qHofYjItVkpsOJtr8f2Ok1kxftBedbQb1fCUpHIEwiUvJSh+HX83ZunVwE9tisoPc/ddQPYFJ+x72Q==
|
||||
dependencies:
|
||||
inferno "7.4.10"
|
||||
|
||||
inferno-create-element@^7.4.2:
|
||||
version "7.4.8"
|
||||
resolved "https://registry.yarnpkg.com/inferno-create-element/-/inferno-create-element-7.4.8.tgz#77bbf24288645c359cf65b4821a3938c6537eb5e"
|
||||
|
@ -4022,13 +4073,6 @@ inferno-create-element@^7.4.2:
|
|||
dependencies:
|
||||
inferno "7.4.8"
|
||||
|
||||
inferno-create-element@^7.4.9:
|
||||
version "7.4.9"
|
||||
resolved "https://registry.yarnpkg.com/inferno-create-element/-/inferno-create-element-7.4.9.tgz#0538b100442163e1c361f2a78664ee3dd5e6f2bb"
|
||||
integrity sha512-wQ/gnd66pdrlm8uPAjGDlSCF6sX9mQ/mGtq8yYKHBTAmWPdE9P3mVsw5Wg9Iyy5NxRVsJqB9emBXIA8PNNkMCg==
|
||||
dependencies:
|
||||
inferno "7.4.9"
|
||||
|
||||
inferno-helmet@^5.2.1:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/inferno-helmet/-/inferno-helmet-5.2.1.tgz#3717f325760aa14abeae82a78af7213f9055a3dc"
|
||||
|
@ -4038,16 +4082,17 @@ inferno-helmet@^5.2.1:
|
|||
inferno-side-effect "^1.1.5"
|
||||
object-assign "^4.1.1"
|
||||
|
||||
inferno-hydrate@^7.4.9:
|
||||
version "7.4.9"
|
||||
resolved "https://registry.yarnpkg.com/inferno-hydrate/-/inferno-hydrate-7.4.9.tgz#ba355f2e17cc273d7adfd957bb19fce4447d8b29"
|
||||
integrity sha512-QP8zmgTddI4WShmQO9VF+wqC3OXmwaGaZkQQNZsp7ZfprhhYuW6ulSPyckWEB/zGZxxYPdG8ZthEeQZF4NS5Jw==
|
||||
inferno-hydrate@^7.4.10:
|
||||
version "7.4.10"
|
||||
resolved "https://registry.yarnpkg.com/inferno-hydrate/-/inferno-hydrate-7.4.10.tgz#678c2423fa47233d905b79d0597b39e1075da12e"
|
||||
integrity sha512-bHJo7wd0ZKAmRlzoHqBjGhEgmOYFBh9LL58bIOeOXiuuyXJFUA6tP/vW91sx7j68K9Zq36SMwtbb/QnQ7R4mug==
|
||||
dependencies:
|
||||
inferno "7.4.9"
|
||||
inferno "7.4.10"
|
||||
|
||||
"inferno-i18next@github:nimbusec-oss/inferno-i18next#semver:^7.4.2":
|
||||
version "7.4.2"
|
||||
resolved "https://codeload.github.com/nimbusec-oss/inferno-i18next/tar.gz/54b9be591ccd62c53799ad23e35f17144a62f909"
|
||||
inferno-i18next-dess@^0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/inferno-i18next-dess/-/inferno-i18next-dess-0.0.1.tgz#48ae6bb4c3a617e59ff8dc97b9ed70f2ef762206"
|
||||
integrity sha512-z/6UnuWFMyBivfR3SI9AmgA0/JvXARqtG/9BQryaLPenlG7iAb4cN2TeSt0mHIgFOo01QHVWZ8dqn7jZRijp2Q==
|
||||
dependencies:
|
||||
html-parse-stringify2 "^2.0.1"
|
||||
inferno "^7.4.2"
|
||||
|
@ -4056,33 +4101,33 @@ inferno-hydrate@^7.4.9:
|
|||
inferno-shared "^7.4.2"
|
||||
inferno-vnode-flags "^7.4.2"
|
||||
|
||||
inferno-router@^7.4.9:
|
||||
version "7.4.9"
|
||||
resolved "https://registry.yarnpkg.com/inferno-router/-/inferno-router-7.4.9.tgz#6f3d708f8e01d47f982b41ae18c9312a4c51220b"
|
||||
integrity sha512-MunV594oIw0lGjcf6HMyPIx/F5bsz2+nz+ao4Jd6cLWy1zUCD8eowqCfZe3Re82wbW8+w7zNp5bCpuV+4IiWNQ==
|
||||
inferno-router@^7.4.10:
|
||||
version "7.4.10"
|
||||
resolved "https://registry.yarnpkg.com/inferno-router/-/inferno-router-7.4.10.tgz#6fb27831b8864204bfab5ed192a2c7280e6420ed"
|
||||
integrity sha512-XIeqmmPVwkRE445uMLqjimsh/zxtCsjA25Vt18y52zZaBH1R3Me6FoEoPsnZhH3j9zyOfzpHpxEPpUGWKfyZoA==
|
||||
dependencies:
|
||||
history "^4.10.1"
|
||||
hoist-non-inferno-statics "^1.1.3"
|
||||
inferno "7.4.9"
|
||||
inferno "7.4.10"
|
||||
path-to-regexp-es6 "1.7.0"
|
||||
|
||||
inferno-server@^7.4.9:
|
||||
version "7.4.9"
|
||||
resolved "https://registry.yarnpkg.com/inferno-server/-/inferno-server-7.4.9.tgz#05e73aa14512f492d9eec30698c55f30c39493bb"
|
||||
integrity sha512-x9kE+Tk34QfRM1OL4+KSWY86t7dLVanRhDWikBs3jsHuFU+bQ9VGuEp4cf4wEBnv0DbPQiH+/XY5aoaQ6S8zFQ==
|
||||
inferno-server@^7.4.10:
|
||||
version "7.4.10"
|
||||
resolved "https://registry.yarnpkg.com/inferno-server/-/inferno-server-7.4.10.tgz#959af7c0946541f29c2258b67a80b7dd11db9df5"
|
||||
integrity sha512-L9BDXUc6nwmZxo2SPeZl3MtvxiBi5fMmutHe7f6uOVlZoFyylg45CY0KS4+1ySxALxW2fb9ZKJJw/8pBXjmgQA==
|
||||
dependencies:
|
||||
inferno "7.4.9"
|
||||
inferno "7.4.10"
|
||||
|
||||
inferno-shared@7.4.10:
|
||||
version "7.4.10"
|
||||
resolved "https://registry.yarnpkg.com/inferno-shared/-/inferno-shared-7.4.10.tgz#d4ca2c7fd6b580f86a623e923d080aa1d7259014"
|
||||
integrity sha512-d7wlcW8NhchfX4vSg+6k9/FwFHAooo81GfWZtnDXtUvZNS4WEMaPH2j1YV6VnN4X3R0850dHRxR7830PdKh4Iw==
|
||||
|
||||
inferno-shared@7.4.8, inferno-shared@^7.4.2:
|
||||
version "7.4.8"
|
||||
resolved "https://registry.yarnpkg.com/inferno-shared/-/inferno-shared-7.4.8.tgz#2b554a36683b770339008749096d9704846dd337"
|
||||
integrity sha512-I0jnqsBcQvGJ7hqZF3vEzspQ80evViCe8joP3snWkPXPElk9WBVGLBHX5tHwuFuXv6wW4zeVVA4kBRAs47B+NQ==
|
||||
|
||||
inferno-shared@7.4.9:
|
||||
version "7.4.9"
|
||||
resolved "https://registry.yarnpkg.com/inferno-shared/-/inferno-shared-7.4.9.tgz#f3cc5e85adadd7471a5e1c72da7df93a0bb98340"
|
||||
integrity sha512-WNBz7OJ1DiVN+NeHgflwBHqvD589B9xMPkFGTj6mNs1cQCxYIZtslp5cqpkEo5TnA8O3FgEF00LMaLfc/i8fyw==
|
||||
|
||||
inferno-side-effect@^1.1.5:
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/inferno-side-effect/-/inferno-side-effect-1.1.5.tgz#a874c80dbc73602aafc1e0f3f3f1ec216a916271"
|
||||
|
@ -4092,15 +4137,24 @@ inferno-side-effect@^1.1.5:
|
|||
npm "^5.8.0"
|
||||
shallowequal "^1.0.1"
|
||||
|
||||
inferno-vnode-flags@7.4.10:
|
||||
version "7.4.10"
|
||||
resolved "https://registry.yarnpkg.com/inferno-vnode-flags/-/inferno-vnode-flags-7.4.10.tgz#a255931fc1df1e6896b29226d837f28e1c5ac968"
|
||||
integrity sha512-OzfnqXrJx8Rl3FtyjhdFk7gzuCLMPCbDTiO8Bz0lw6P3ngW9Md5N5LPeg7Jz510PM0NisScQtxxHPwsFQxDIfw==
|
||||
|
||||
inferno-vnode-flags@7.4.8, inferno-vnode-flags@^7.4.2:
|
||||
version "7.4.8"
|
||||
resolved "https://registry.yarnpkg.com/inferno-vnode-flags/-/inferno-vnode-flags-7.4.8.tgz#275d70e3c8b2b3f4eb56041cc9b8c832ce1fb26d"
|
||||
integrity sha512-wOUeO7Aho8VH+s2V2K/53KwS0DtQFgT7TdzPE/s6P26ZIxQj+vt7oTJqzXn+xjRIjnfkTLm2eQ8qfInOWCu1rw==
|
||||
|
||||
inferno-vnode-flags@7.4.9:
|
||||
version "7.4.9"
|
||||
resolved "https://registry.yarnpkg.com/inferno-vnode-flags/-/inferno-vnode-flags-7.4.9.tgz#26fe1a40f00de2ebc05b9e7543c8577c674c074b"
|
||||
integrity sha512-pIGvc1MRSRrvjAOpARRz9OCyGrODKYfhw02ctWTIt3Jtn8dyqsjV0ZIaXOQEjpCi5ii7yfE5xwI+ZRI/g3pviQ==
|
||||
inferno@7.4.10, inferno@^7.4.10:
|
||||
version "7.4.10"
|
||||
resolved "https://registry.yarnpkg.com/inferno/-/inferno-7.4.10.tgz#edfeca0db7dd1790aaf60e6aaf7edae8fe97dc63"
|
||||
integrity sha512-L/qPVapN/b4WSrQND6fN0LOvhIeCVpGFQRbDplZvovOJoxRRZyE21k92tL/C76hQVFOp2FIgjZ7fjy9AnKPS+A==
|
||||
dependencies:
|
||||
inferno-shared "7.4.10"
|
||||
inferno-vnode-flags "7.4.10"
|
||||
opencollective-postinstall "^2.0.3"
|
||||
|
||||
inferno@7.4.8, inferno@^7.4.2:
|
||||
version "7.4.8"
|
||||
|
@ -4111,15 +4165,6 @@ inferno@7.4.8, inferno@^7.4.2:
|
|||
inferno-vnode-flags "7.4.8"
|
||||
opencollective-postinstall "^2.0.3"
|
||||
|
||||
inferno@7.4.9, inferno@^7.4.9:
|
||||
version "7.4.9"
|
||||
resolved "https://registry.yarnpkg.com/inferno/-/inferno-7.4.9.tgz#3722319b53b7e902ab194ad26fa6e80332d85c47"
|
||||
integrity sha512-YxUYo3CyFGRkeSne87DacSAV1yXOp6dAu0toaEkwxb4dIIMilxDmJ8ap0EKxr3ZnQpX7EKmGrLBXrkYQVcmfvg==
|
||||
dependencies:
|
||||
inferno-shared "7.4.9"
|
||||
inferno-vnode-flags "7.4.9"
|
||||
opencollective-postinstall "^2.0.3"
|
||||
|
||||
inflight@^1.0.4, inflight@~1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
|
||||
|
@ -4715,6 +4760,13 @@ lines-and-columns@^1.1.6:
|
|||
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
|
||||
integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
|
||||
|
||||
linkify-it@^2.0.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.2.0.tgz#e3b54697e78bf915c70a38acd78fd09e0058b1cf"
|
||||
integrity sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==
|
||||
dependencies:
|
||||
uc.micro "^1.0.1"
|
||||
|
||||
linkify-it@^3.0.1:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.2.tgz#f55eeb8bc1d3ae754049e124ab3bb56d97797fb8"
|
||||
|
@ -5025,6 +5077,14 @@ markdown-it-container@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/markdown-it-container/-/markdown-it-container-3.0.0.tgz#1d19b06040a020f9a827577bb7dbf67aa5de9a5b"
|
||||
integrity sha512-y6oKTq4BB9OQuY/KLfk/O3ysFhB3IMYoIWhGJEidXt1NQFocFK2sA2t0NYZAMyMShAGL6x5OPIbrmXPIqaN9rw==
|
||||
|
||||
markdown-it-html5-embed@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/markdown-it-html5-embed/-/markdown-it-html5-embed-1.0.0.tgz#f36bedca1eb12ce4df2d53b5ec72f62ba5e094b3"
|
||||
integrity sha512-SPgugO/1+/9sZcgxoxijoTHSUpCUgFCNe1MSuTmDxDkV6NQrVzMclhRMFgE/rcHO+2rhIg3U7Oy80XA/E8ytpg==
|
||||
dependencies:
|
||||
markdown-it "^8.4.0"
|
||||
mimoza "~1.0.0"
|
||||
|
||||
markdown-it-sub@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/markdown-it-sub/-/markdown-it-sub-1.0.0.tgz#375fd6026eae7ddcb012497f6411195ea1e3afe8"
|
||||
|
@ -5046,6 +5106,17 @@ markdown-it@^12.1.0:
|
|||
mdurl "^1.0.1"
|
||||
uc.micro "^1.0.5"
|
||||
|
||||
markdown-it@^8.4.0:
|
||||
version "8.4.2"
|
||||
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-8.4.2.tgz#386f98998dc15a37722aa7722084f4020bdd9b54"
|
||||
integrity sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ==
|
||||
dependencies:
|
||||
argparse "^1.0.7"
|
||||
entities "~1.1.1"
|
||||
linkify-it "^2.0.0"
|
||||
mdurl "^1.0.1"
|
||||
uc.micro "^1.0.5"
|
||||
|
||||
mdurl@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
|
||||
|
@ -5134,6 +5205,11 @@ mime-db@1.49.0, "mime-db@>= 1.43.0 < 2":
|
|||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.49.0.tgz#f3dfde60c99e9cf3bc9701d687778f537001cbed"
|
||||
integrity sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==
|
||||
|
||||
mime-db@^1.6.0:
|
||||
version "1.50.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.50.0.tgz#abd4ac94e98d3c0e185016c67ab45d5fde40c11f"
|
||||
integrity sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==
|
||||
|
||||
mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24:
|
||||
version "2.1.32"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.32.tgz#1d00e89e7de7fe02008db61001d9e02852670fd5"
|
||||
|
@ -5161,6 +5237,13 @@ mimic-fn@^3.1.0:
|
|||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-3.1.0.tgz#65755145bbf3e36954b949c16450427451d5ca74"
|
||||
integrity sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==
|
||||
|
||||
mimoza@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/mimoza/-/mimoza-1.0.0.tgz#d74aa4fe08932f005e430bdc7bfcfa95fcab4e62"
|
||||
integrity sha1-10qk/giTLwBeQwvce/z6lfyrTmI=
|
||||
dependencies:
|
||||
mime-db "^1.6.0"
|
||||
|
||||
min-indent@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
|
||||
|
@ -8350,11 +8433,16 @@ write-file-atomic@^2.0.0, write-file-atomic@^2.3.0:
|
|||
imurmurhash "^0.1.4"
|
||||
signal-exit "^3.0.2"
|
||||
|
||||
ws@^8.1.0, ws@^8.2.0:
|
||||
ws@^8.1.0:
|
||||
version "8.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.0.tgz#0b738cd484bfc9303421914b11bb4011e07615bb"
|
||||
integrity sha512-uYhVJ/m9oXwEI04iIVmgLmugh2qrZihkywG9y5FfZV2ATeLIzHf93qs+tUNqlttbQK957/VX3mtwAS+UfIwA4g==
|
||||
|
||||
ws@^8.2.2:
|
||||
version "8.2.2"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.2.tgz#ca684330c6dd6076a737250ed81ac1606cb0a63e"
|
||||
integrity sha512-Q6B6H2oc8QY3llc3cB8kVmQ6pnJWVQbP7Q5algTcIxx7YEpc0oU4NBVHlztA7Ekzfhw2r0rPducMUiCGWKQRzw==
|
||||
|
||||
xdg-basedir@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"
|
||||
|
|
Loading…
Reference in a new issue