Merge branch 'main' into more_accessibility

This commit is contained in:
Dessalines 2021-02-11 15:43:06 -05:00
commit 58bbb51f8f
33 changed files with 246 additions and 423 deletions

View file

@ -11,6 +11,10 @@
cursor: pointer; cursor: pointer;
} }
.pointer-events {
pointer-events: auto !important;
}
.no-click { .no-click {
pointer-events:none; pointer-events:none;
opacity: 0.65; opacity: 0.65;

View file

@ -25,6 +25,7 @@ import autosize from 'autosize';
import { SiteForm } from './site-form'; import { SiteForm } from './site-form';
import { UserListing } from './user-listing'; import { UserListing } from './user-listing';
import { HtmlTags } from './html-tags'; import { HtmlTags } from './html-tags';
import { Spinner } from './icon';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { InitialFetchRequest } from 'shared/interfaces'; import { InitialFetchRequest } from 'shared/interfaces';
@ -109,9 +110,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
/> />
{this.state.loading ? ( {this.state.loading ? (
<h5> <h5>
<svg class="icon icon-spinner spin"> <Spinner />
<use xlinkHref="#icon-spinner"></use>
</svg>
</h5> </h5>
) : ( ) : (
<div class="row"> <div class="row">
@ -185,9 +184,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
<div class="col-12"> <div class="col-12">
<button type="submit" class="btn btn-secondary mr-2"> <button type="submit" class="btn btn-secondary mr-2">
{this.state.siteConfigLoading ? ( {this.state.siteConfigLoading ? (
<svg class="icon icon-spinner spin"> <Spinner />
<use xlinkHref="#icon-spinner"></use>
</svg>
) : ( ) : (
capitalizeFirstLetter(i18n.t('save')) capitalizeFirstLetter(i18n.t('save'))
)} )}

View file

@ -1,5 +1,6 @@
import { Component } from 'inferno'; import { Component } from 'inferno';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { Icon } from './icon';
interface CakeDayProps { interface CakeDayProps {
creatorName: string; creatorName: string;
@ -12,9 +13,7 @@ export class CakeDay extends Component<CakeDayProps, any> {
className={`mx-2 d-inline-block unselectable pointer`} className={`mx-2 d-inline-block unselectable pointer`}
data-tippy-content={this.cakeDayTippy()} data-tippy-content={this.cakeDayTippy()}
> >
<svg class="icon icon-inline"> <Icon icon="cake" classes="icon-inline" />
<use xlinkHref="#icon-cake"></use>
</svg>
</div> </div>
); );
} }

View file

@ -20,6 +20,7 @@ import { WebSocketService, UserService } from '../services';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { T } from 'inferno-i18next'; import { T } from 'inferno-i18next';
import { MarkdownTextArea } from './markdown-textarea'; import { MarkdownTextArea } from './markdown-textarea';
import { Icon } from './icon';
interface CommentFormProps { interface CommentFormProps {
postId?: number; postId?: number;
@ -84,9 +85,7 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
/> />
) : ( ) : (
<div class="alert alert-light" role="alert"> <div class="alert alert-light" role="alert">
<svg class="icon icon-inline mr-2"> <Icon icon="alert-triangle" classes="icon-inline mr-2" />
<use xlinkHref="#icon-alert-triangle"></use>
</svg>
<T i18nKey="must_login" class="d-inline"> <T i18nKey="must_login" class="d-inline">
# #
<Link className="alert-link" to="/login"> <Link className="alert-link" to="/login">

View file

@ -36,6 +36,7 @@ import { CommentForm } from './comment-form';
import { CommentNodes } from './comment-nodes'; import { CommentNodes } from './comment-nodes';
import { UserListing } from './user-listing'; import { UserListing } from './user-listing';
import { CommunityLink } from './community-link'; import { CommunityLink } from './community-link';
import { Icon, Spinner } from './icon';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
interface CommentNodeState { interface CommentNodeState {
@ -260,13 +261,12 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
{this.state.readLoading ? ( {this.state.readLoading ? (
this.loadingIcon this.loadingIcon
) : ( ) : (
<svg <Icon
class={`icon icon-inline ${ icon="check"
classes={`icon-inline ${
this.commentOrMentionRead && 'text-success' this.commentOrMentionRead && 'text-success'
}`} }`}
> />
<use xlinkHref="#icon-check"></use>
</svg>
)} )}
</button> </button>
)} )}
@ -280,9 +280,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
data-tippy-content={i18n.t('upvote')} data-tippy-content={i18n.t('upvote')}
aria-label={i18n.t('upvote')} aria-label={i18n.t('upvote')}
> >
<svg class="icon icon-inline"> <Icon icon="arrow-up1" classes="icon-inline" />
<use xlinkHref="#icon-arrow-up1"></use>
</svg>
{this.state.upvotes !== this.state.score && ( {this.state.upvotes !== this.state.score && (
<span class="ml-1">{this.state.upvotes}</span> <span class="ml-1">{this.state.upvotes}</span>
)} )}
@ -298,9 +296,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
data-tippy-content={i18n.t('downvote')} data-tippy-content={i18n.t('downvote')}
aria-label={i18n.t('downvote')} aria-label={i18n.t('downvote')}
> >
<svg class="icon icon-inline"> <Icon icon="arrow-down1" classes="icon-inline" />
<use xlinkHref="#icon-arrow-down1"></use>
</svg>
{this.state.upvotes !== this.state.score && ( {this.state.upvotes !== this.state.score && (
<span class="ml-1">{this.state.downvotes}</span> <span class="ml-1">{this.state.downvotes}</span>
)} )}
@ -312,9 +308,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
data-tippy-content={i18n.t('reply')} data-tippy-content={i18n.t('reply')}
aria-label={i18n.t('reply')} aria-label={i18n.t('reply')}
> >
<svg class="icon icon-inline"> <Icon icon="reply1" classes="icon-inline" />
<use xlinkHref="#icon-reply1"></use>
</svg>
</button> </button>
{!this.state.showAdvanced ? ( {!this.state.showAdvanced ? (
<button <button
@ -323,9 +317,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
data-tippy-content={i18n.t('more')} data-tippy-content={i18n.t('more')}
aria-label={i18n.t('more')} aria-label={i18n.t('more')}
> >
<svg class="icon icon-inline"> <Icon icon="more-vertical" classes="icon-inline" />
<use xlinkHref="#icon-more-vertical"></use>
</svg>
</button> </button>
) : ( ) : (
<> <>
@ -336,9 +328,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
to={`/create_private_message/recipient/${cv.creator.id}`} to={`/create_private_message/recipient/${cv.creator.id}`}
title={i18n.t('message').toLowerCase()} title={i18n.t('message').toLowerCase()}
> >
<svg class="icon"> <Icon icon="mail" />
<use xlinkHref="#icon-mail"></use>
</svg>
</Link> </Link>
</button> </button>
)} )}
@ -359,13 +349,12 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
{this.state.saveLoading ? ( {this.state.saveLoading ? (
this.loadingIcon this.loadingIcon
) : ( ) : (
<svg <Icon
class={`icon icon-inline ${ icon="star"
classes={`icon-inline ${
cv.saved && 'text-warning' cv.saved && 'text-warning'
}`} }`}
> />
<use xlinkHref="#icon-star"></use>
</svg>
)} )}
</button> </button>
<button <button
@ -374,13 +363,12 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
data-tippy-content={i18n.t('view_source')} data-tippy-content={i18n.t('view_source')}
aria-label={i18n.t('view_source')} aria-label={i18n.t('view_source')}
> >
<svg <Icon
class={`icon icon-inline ${ icon="file-text"
classes={`icon-inline ${
this.state.viewSource && 'text-success' this.state.viewSource && 'text-success'
}`} }`}
> />
<use xlinkHref="#icon-file-text"></use>
</svg>
</button> </button>
{this.myComment && ( {this.myComment && (
<> <>
@ -390,9 +378,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
data-tippy-content={i18n.t('edit')} data-tippy-content={i18n.t('edit')}
aria-label={i18n.t('edit')} aria-label={i18n.t('edit')}
> >
<svg class="icon icon-inline"> <Icon icon="edit" classes="icon-inline" />
<use xlinkHref="#icon-edit"></use>
</svg>
</button> </button>
<button <button
class="btn btn-link btn-animate text-muted" class="btn btn-link btn-animate text-muted"
@ -411,13 +397,12 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
: i18n.t('restore') : i18n.t('restore')
} }
> >
<svg <Icon
class={`icon icon-inline ${ icon="trash"
classes={`icon-inline ${
cv.comment.deleted && 'text-danger' cv.comment.deleted && 'text-danger'
}`} }`}
> />
<use xlinkHref="#icon-trash"></use>
</svg>
</button> </button>
</> </>
)} )}
@ -817,19 +802,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
to={`/post/${cv.post.id}/comment/${cv.comment.id}`} to={`/post/${cv.post.id}/comment/${cv.comment.id}`}
title={this.props.showContext ? i18n.t('show_context') : i18n.t('link')} title={this.props.showContext ? i18n.t('show_context') : i18n.t('link')}
> >
<svg class="icon icon-inline"> <Icon icon="link" classes="icon-inline" />
<use xlinkHref="#icon-link"></use>
</svg>
</Link> </Link>
); );
} }
get loadingIcon() { get loadingIcon() {
return ( return <Spinner />;
<svg class="icon icon-spinner spin">
<use xlinkHref="#icon-spinner"></use>
</svg>
);
} }
get myComment(): boolean { get myComment(): boolean {

View file

@ -26,6 +26,7 @@ import {
setOptionalAuth, setOptionalAuth,
} from '../utils'; } from '../utils';
import { CommunityLink } from './community-link'; import { CommunityLink } from './community-link';
import { Spinner } from './icon';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { InitialFetchRequest } from 'shared/interfaces'; import { InitialFetchRequest } from 'shared/interfaces';
@ -104,10 +105,8 @@ export class Communities extends Component<any, CommunitiesState> {
path={this.context.router.route.match.url} path={this.context.router.route.match.url}
/> />
{this.state.loading ? ( {this.state.loading ? (
<h5 class=""> <h5>
<svg class="icon icon-spinner spin"> <Spinner />
<use xlinkHref="#icon-spinner"></use>
</svg>
</h5> </h5>
) : ( ) : (
<div> <div>

View file

@ -24,6 +24,7 @@ import { i18n } from '../i18next';
import { MarkdownTextArea } from './markdown-textarea'; import { MarkdownTextArea } from './markdown-textarea';
import { ImageUploadForm } from './image-upload-form'; import { ImageUploadForm } from './image-upload-form';
import { Icon, Spinner } from './icon';
interface CommunityFormProps { interface CommunityFormProps {
community_view?: CommunityView; // If a community is given, that means this is an edit community_view?: CommunityView; // If a community is given, that means this is an edit
@ -132,9 +133,7 @@ export class CommunityForm extends Component<
class="pointer unselectable ml-2 text-muted" class="pointer unselectable ml-2 text-muted"
data-tippy-content={i18n.t('name_explain')} data-tippy-content={i18n.t('name_explain')}
> >
<svg class="icon icon-inline"> <Icon icon="help-circle" classes="icon-inline" />
<use xlinkHref="#icon-help-circle"></use>
</svg>
</span> </span>
</label> </label>
<div class="col-12"> <div class="col-12">
@ -160,9 +159,7 @@ export class CommunityForm extends Component<
class="pointer unselectable ml-2 text-muted" class="pointer unselectable ml-2 text-muted"
data-tippy-content={i18n.t('display_name_explain')} data-tippy-content={i18n.t('display_name_explain')}
> >
<svg class="icon icon-inline"> <Icon icon="help-circle" classes="icon-inline" />
<use xlinkHref="#icon-help-circle"></use>
</svg>
</span> </span>
</label> </label>
<div class="col-12"> <div class="col-12">
@ -252,9 +249,7 @@ export class CommunityForm extends Component<
disabled={this.state.loading} disabled={this.state.loading}
> >
{this.state.loading ? ( {this.state.loading ? (
<svg class="icon icon-spinner spin"> <Spinner />
<use xlinkHref="#icon-spinner"></use>
</svg>
) : this.props.community_view ? ( ) : this.props.community_view ? (
capitalizeFirstLetter(i18n.t('save')) capitalizeFirstLetter(i18n.t('save'))
) : ( ) : (

View file

@ -31,6 +31,7 @@ import { DataTypeSelect } from './data-type-select';
import { Sidebar } from './sidebar'; import { Sidebar } from './sidebar';
import { CommunityLink } from './community-link'; import { CommunityLink } from './community-link';
import { BannerIconHeader } from './banner-icon-header'; import { BannerIconHeader } from './banner-icon-header';
import { Icon, Spinner } from './icon';
import { import {
wsJsonToRes, wsJsonToRes,
fetchLimit, fetchLimit,
@ -244,9 +245,7 @@ export class Community extends Component<any, State> {
<div class="container"> <div class="container">
{this.state.communityLoading ? ( {this.state.communityLoading ? (
<h5> <h5>
<svg class="icon icon-spinner spin"> <Spinner />
<use xlinkHref="#icon-spinner"></use>
</svg>
</h5> </h5>
) : ( ) : (
<div class="row"> <div class="row">
@ -283,9 +282,7 @@ export class Community extends Component<any, State> {
return this.state.dataType == DataType.Post ? ( return this.state.dataType == DataType.Post ? (
this.state.postsLoading ? ( this.state.postsLoading ? (
<h5> <h5>
<svg class="icon icon-spinner spin"> <Spinner />
<use xlinkHref="#icon-spinner"></use>
</svg>
</h5> </h5>
) : ( ) : (
<PostListings <PostListings
@ -297,9 +294,7 @@ export class Community extends Component<any, State> {
) )
) : this.state.commentsLoading ? ( ) : this.state.commentsLoading ? (
<h5> <h5>
<svg class="icon icon-spinner spin"> <Spinner />
<use xlinkHref="#icon-spinner"></use>
</svg>
</h5> </h5>
) : ( ) : (
<CommentNodes <CommentNodes
@ -350,9 +345,7 @@ export class Community extends Component<any, State> {
title="RSS" title="RSS"
rel="noopener" rel="noopener"
> >
<svg class="icon text-muted small"> <Icon icon="rss" classes="text-muted small" />
<use xlinkHref="#icon-rss"></use>
</svg>
</a> </a>
</div> </div>
); );

View file

@ -2,6 +2,7 @@ import { Component } from 'inferno';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { CommunityForm } from './community-form'; import { CommunityForm } from './community-form';
import { HtmlTags } from './html-tags'; import { HtmlTags } from './html-tags';
import { Spinner } from './icon';
import { import {
CommunityView, CommunityView,
UserOperation, UserOperation,
@ -77,9 +78,7 @@ export class CreateCommunity extends Component<any, CreateCommunityState> {
/> />
{this.state.loading ? ( {this.state.loading ? (
<h5> <h5>
<svg class="icon icon-spinner spin"> <Spinner />
<use xlinkHref="#icon-spinner"></use>
</svg>
</h5> </h5>
) : ( ) : (
<div class="row"> <div class="row">

View file

@ -2,6 +2,7 @@ import { Component } from 'inferno';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { PostForm } from './post-form'; import { PostForm } from './post-form';
import { HtmlTags } from './html-tags'; import { HtmlTags } from './html-tags';
import { Spinner } from './icon';
import { import {
authField, authField,
isBrowser, isBrowser,
@ -95,9 +96,7 @@ export class CreatePost extends Component<any, CreatePostState> {
/> />
{this.state.loading ? ( {this.state.loading ? (
<h5> <h5>
<svg class="icon icon-spinner spin"> <Spinner />
<use xlinkHref="#icon-spinner"></use>
</svg>
</h5> </h5>
) : ( ) : (
<div class="row"> <div class="row">

View file

@ -2,6 +2,7 @@ import { Component } from 'inferno';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { PrivateMessageForm } from './private-message-form'; import { PrivateMessageForm } from './private-message-form';
import { HtmlTags } from './html-tags'; import { HtmlTags } from './html-tags';
import { Spinner } from './icon';
import { UserService, WebSocketService } from '../services'; import { UserService, WebSocketService } from '../services';
import { import {
SiteView, SiteView,
@ -112,9 +113,7 @@ export class CreatePrivateMessage extends Component<
/> />
{this.state.loading ? ( {this.state.loading ? (
<h5> <h5>
<svg class="icon icon-spinner spin"> <Spinner />
<use xlinkHref="#icon-spinner"></use>
</svg>
</h5> </h5>
) : ( ) : (
<div class="row"> <div class="row">

View file

@ -0,0 +1,31 @@
import { Component } from 'inferno';
interface IconProps {
icon: string;
classes?: string;
}
export class Icon extends Component<IconProps, any> {
constructor(props: any, context: any) {
super(props, context);
}
render() {
return (
<svg class={`icon ${this.props.classes}`}>
<title>{this.props.icon}</title>
<use xlinkHref={`#icon-${this.props.icon}`}></use>
</svg>
);
}
}
export class Spinner extends Component<any, any> {
constructor(props: any, context: any) {
super(props, context);
}
render() {
return <Icon icon="spinner" classes="icon-spinner spin" />;
}
}

View file

@ -2,6 +2,7 @@ import { Component, linkEvent } from 'inferno';
import { Post } from 'lemmy-js-client'; import { Post } from 'lemmy-js-client';
import { mdToHtml } from '../utils'; import { mdToHtml } from '../utils';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { Icon } from './icon';
interface FramelyCardProps { interface FramelyCardProps {
post: Post; post: Post;
@ -52,9 +53,7 @@ export class IFramelyCard extends Component<
rel="noopener" rel="noopener"
> >
{new URL(post.url).hostname} {new URL(post.url).hostname}
<svg class="ml-1 icon"> <Icon icon="external-link" classes="ml-1" />
<use xlinkHref="#icon-external-link"></use>
</svg>
</a> </a>
</span>, </span>,
]} ]}

View file

@ -3,6 +3,7 @@ import { pictrsUri } from '../env';
import { UserService } from '../services'; import { UserService } from '../services';
import { toast, randomStr } from '../utils'; import { toast, randomStr } from '../utils';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { Icon } from './icon';
interface ImageUploadFormProps { interface ImageUploadFormProps {
uploadTitle: string; uploadTitle: string;
@ -53,9 +54,7 @@ export class ImageUploadForm extends Component<
onClick={linkEvent(this, this.handleRemoveImage)} onClick={linkEvent(this, this.handleRemoveImage)}
aria-label={i18n.t('remove')} aria-label={i18n.t('remove')}
> >
<svg class="icon mini-overlay"> <Icon icon="x" classes="mini-overlay" />
<use xlinkHref="#icon-x"></use>
</svg>
</a> </a>
</span> </span>
)} )}

View file

@ -38,6 +38,7 @@ import { CommentNodes } from './comment-nodes';
import { PrivateMessage } from './private-message'; import { PrivateMessage } from './private-message';
import { HtmlTags } from './html-tags'; import { HtmlTags } from './html-tags';
import { SortSelect } from './sort-select'; import { SortSelect } from './sort-select';
import { Icon, Spinner } from './icon';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { InitialFetchRequest } from 'shared/interfaces'; import { InitialFetchRequest } from 'shared/interfaces';
@ -137,9 +138,7 @@ export class Inbox extends Component<any, InboxState> {
<div class="container"> <div class="container">
{this.state.loading ? ( {this.state.loading ? (
<h5> <h5>
<svg class="icon icon-spinner spin"> <Spinner />
<use xlinkHref="#icon-spinner"></use>
</svg>
</h5> </h5>
) : ( ) : (
<div class="row"> <div class="row">
@ -157,9 +156,7 @@ export class Inbox extends Component<any, InboxState> {
title="RSS" title="RSS"
rel="noopener" rel="noopener"
> >
<svg class="icon ml-2 text-muted small"> <Icon icon="rss" classes="ml-2 text-muted small" />
<use xlinkHref="#icon-rss"></use>
</svg>
</a> </a>
</small> </small>
</h5> </h5>

View file

@ -24,6 +24,7 @@ import {
} from '../utils'; } from '../utils';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { HtmlTags } from './html-tags'; import { HtmlTags } from './html-tags';
import { Icon, Spinner } from './icon';
interface State { interface State {
loginForm: LoginForm; loginForm: LoginForm;
@ -134,27 +135,21 @@ export class Login extends Component<any, State> {
class="form-control" class="form-control"
required required
/> />
{validEmail(this.state.loginForm.username_or_email) && ( <button
<button type="button"
type="button" onClick={linkEvent(this, this.handlePasswordReset)}
onClick={linkEvent(this, this.handlePasswordReset)} className="btn p-0 btn-link d-inline-block float-right text-muted small font-weight-bold pointer-events"
className="btn p-0 btn-link d-inline-block float-right text-muted small font-weight-bold" disabled={!validEmail(this.state.loginForm.username_or_email)}
> title={i18n.t('no_password_reset')}
{i18n.t('forgot_password')} >
</button> {i18n.t('forgot_password')}
)} </button>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<div class="col-sm-10"> <div class="col-sm-10">
<button type="submit" class="btn btn-secondary"> <button type="submit" class="btn btn-secondary">
{this.state.loginLoading ? ( {this.state.loginLoading ? <Spinner /> : i18n.t('login')}
<svg class="icon icon-spinner spin">
<use xlinkHref="#icon-spinner"></use>
</svg>
) : (
i18n.t('login')
)}
</button> </button>
</div> </div>
</div> </div>
@ -204,9 +199,7 @@ export class Login extends Component<any, State> {
/> />
{!validEmail(this.state.registerForm.email) && ( {!validEmail(this.state.registerForm.email) && (
<div class="mt-2 mb-0 alert alert-light" role="alert"> <div class="mt-2 mb-0 alert alert-light" role="alert">
<svg class="icon icon-inline mr-2"> <Icon icon="alert-triangle" classes="icon-inline mr-2" />
<use xlinkHref="#icon-alert-triangle"></use>
</svg>
{i18n.t('no_password_reset')} {i18n.t('no_password_reset')}
</div> </div>
)} )}
@ -262,9 +255,7 @@ export class Login extends Component<any, State> {
onClick={linkEvent(this, this.handleRegenCaptcha)} onClick={linkEvent(this, this.handleRegenCaptcha)}
aria-label={i18n.t('captcha')} aria-label={i18n.t('captcha')}
> >
<svg class="icon icon-refresh-cw"> <Icon icon="refresh-cw" classes="icon-refresh-cw" />
<use xlinkHref="#icon-refresh-cw"></use>
</svg>
</button> </button>
</label> </label>
{this.showCaptcha()} {this.showCaptcha()}
@ -304,13 +295,7 @@ export class Login extends Component<any, State> {
<div class="form-group row"> <div class="form-group row">
<div class="col-sm-10"> <div class="col-sm-10">
<button type="submit" class="btn btn-secondary"> <button type="submit" class="btn btn-secondary">
{this.state.registerLoading ? ( {this.state.registerLoading ? <Spinner /> : i18n.t('sign_up')}
<svg class="icon icon-spinner spin">
<use xlinkHref="#icon-spinner"></use>
</svg>
) : (
i18n.t('sign_up')
)}
</button> </button>
</div> </div>
</div> </div>
@ -338,9 +323,7 @@ export class Login extends Component<any, State> {
type="button" type="button"
disabled={this.state.captchaPlaying} disabled={this.state.captchaPlaying}
> >
<svg class="icon icon-play"> <Icon icon="play" classes="icon-play" />
<use xlinkHref="#icon-play"></use>
</svg>
</button> </button>
)} )}
</> </>

View file

@ -34,6 +34,7 @@ import { SiteForm } from './site-form';
import { UserListing } from './user-listing'; import { UserListing } from './user-listing';
import { CommunityLink } from './community-link'; import { CommunityLink } from './community-link';
import { BannerIconHeader } from './banner-icon-header'; import { BannerIconHeader } from './banner-icon-header';
import { Icon, Spinner } from './icon';
import { import {
wsJsonToRes, wsJsonToRes,
mdToHtml, mdToHtml,
@ -513,9 +514,7 @@ export class Main extends Component<any, MainState> {
aria-label={i18n.t('edit')} aria-label={i18n.t('edit')}
data-tippy-content={i18n.t('edit')} data-tippy-content={i18n.t('edit')}
> >
<svg class="icon icon-inline"> <Icon icon="edit" classes="icon-inline" />
<use xlinkHref="#icon-edit"></use>
</svg>
</span> </span>
</li> </li>
</ul> </ul>
@ -539,9 +538,7 @@ export class Main extends Component<any, MainState> {
<div class="main-content-wrapper"> <div class="main-content-wrapper">
{this.state.loading ? ( {this.state.loading ? (
<h5> <h5>
<svg class="icon icon-spinner spin"> <Spinner />
<use xlinkHref="#icon-spinner"></use>
</svg>
</h5> </h5>
) : ( ) : (
<div> <div>
@ -601,9 +598,7 @@ export class Main extends Component<any, MainState> {
rel="noopener" rel="noopener"
title="RSS" title="RSS"
> >
<svg class="icon text-muted small"> <Icon icon="rss" classes="text-muted small" />
<use xlinkHref="#icon-rss"></use>
</svg>
</a> </a>
)} )}
{this.state.listingType == ListingType.Local && ( {this.state.listingType == ListingType.Local && (
@ -613,9 +608,7 @@ export class Main extends Component<any, MainState> {
rel="noopener" rel="noopener"
title="RSS" title="RSS"
> >
<svg class="icon text-muted small"> <Icon icon="rss" classes="text-muted small" />
<use xlinkHref="#icon-rss">#</use>
</svg>
</a> </a>
)} )}
{UserService.Instance.user && {UserService.Instance.user &&
@ -626,9 +619,7 @@ export class Main extends Component<any, MainState> {
title="RSS" title="RSS"
rel="noopener" rel="noopener"
> >
<svg class="icon text-muted small"> <Icon icon="rss" classes="text-muted small" />
<use xlinkHref="#icon-rss">#</use>
</svg>
</a> </a>
)} )}
</div> </div>

View file

@ -14,6 +14,7 @@ import { UserService } from '../services';
import autosize from 'autosize'; import autosize from 'autosize';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { pictrsUri } from '../env'; import { pictrsUri } from '../env';
import { Icon, Spinner } from './icon';
interface MarkdownTextAreaProps { interface MarkdownTextAreaProps {
initialContent: string; initialContent: string;
@ -151,9 +152,7 @@ export class MarkdownTextArea extends Component<
disabled={this.props.disabled || this.state.loading} disabled={this.props.disabled || this.state.loading}
> >
{this.state.loading ? ( {this.state.loading ? (
<svg class="icon icon-spinner spin"> <Spinner />
<use xlinkHref="#icon-spinner"></use>
</svg>
) : ( ) : (
<span>{this.props.buttonTitle}</span> <span>{this.props.buttonTitle}</span>
)} )}
@ -186,9 +185,7 @@ export class MarkdownTextArea extends Component<
aria-label={i18n.t('bold')} aria-label={i18n.t('bold')}
onClick={linkEvent(this, this.handleInsertBold)} onClick={linkEvent(this, this.handleInsertBold)}
> >
<svg class="icon icon-inline"> <Icon icon="bold" classes="icon-inline" />
<use xlinkHref="#icon-bold"></use>
</svg>
</button> </button>
<button <button
class="btn btn-sm text-muted" class="btn btn-sm text-muted"
@ -196,9 +193,7 @@ export class MarkdownTextArea extends Component<
aria-label={i18n.t('italic')} aria-label={i18n.t('italic')}
onClick={linkEvent(this, this.handleInsertItalic)} onClick={linkEvent(this, this.handleInsertItalic)}
> >
<svg class="icon icon-inline"> <Icon icon="italic" classes="icon-inline" />
<use xlinkHref="#icon-italic"></use>
</svg>
</button> </button>
<button <button
class="btn btn-sm text-muted" class="btn btn-sm text-muted"
@ -206,9 +201,7 @@ export class MarkdownTextArea extends Component<
aria-label={i18n.t('link')} aria-label={i18n.t('link')}
onClick={linkEvent(this, this.handleInsertLink)} onClick={linkEvent(this, this.handleInsertLink)}
> >
<svg class="icon icon-inline"> <Icon icon="link" classes="icon-inline" />
<use xlinkHref="#icon-link"></use>
</svg>
</button> </button>
<form class="btn btn-sm text-muted font-weight-bold"> <form class="btn btn-sm text-muted font-weight-bold">
<label <label
@ -217,13 +210,9 @@ export class MarkdownTextArea extends Component<
data-tippy-content={i18n.t('upload_image')} data-tippy-content={i18n.t('upload_image')}
> >
{this.state.imageLoading ? ( {this.state.imageLoading ? (
<svg class="icon icon-spinner spin"> <Spinner />
<use xlinkHref="#icon-spinner"></use>
</svg>
) : ( ) : (
<svg class="icon icon-inline"> <Icon icon="image" classes="icon-inline" />
<use xlinkHref="#icon-image"></use>
</svg>
)} )}
</label> </label>
<input <input
@ -242,9 +231,7 @@ export class MarkdownTextArea extends Component<
aria-label={i18n.t('header')} aria-label={i18n.t('header')}
onClick={linkEvent(this, this.handleInsertHeader)} onClick={linkEvent(this, this.handleInsertHeader)}
> >
<svg class="icon icon-inline"> <Icon icon="header" classes="icon-inline" />
<use xlinkHref="#icon-header"></use>
</svg>
</button> </button>
<button <button
class="btn btn-sm text-muted" class="btn btn-sm text-muted"
@ -252,9 +239,7 @@ export class MarkdownTextArea extends Component<
aria-label={i18n.t('strikethrough')} aria-label={i18n.t('strikethrough')}
onClick={linkEvent(this, this.handleInsertStrikethrough)} onClick={linkEvent(this, this.handleInsertStrikethrough)}
> >
<svg class="icon icon-inline"> <Icon icon="strikethrough" classes="icon-inline" />
<use xlinkHref="#icon-strikethrough"></use>
</svg>
</button> </button>
<button <button
class="btn btn-sm text-muted" class="btn btn-sm text-muted"
@ -262,9 +247,7 @@ export class MarkdownTextArea extends Component<
aria-label={i18n.t('quote')} aria-label={i18n.t('quote')}
onClick={linkEvent(this, this.handleInsertQuote)} onClick={linkEvent(this, this.handleInsertQuote)}
> >
<svg class="icon icon-inline"> <Icon icon="format_quote" classes="icon-inline" />
<use xlinkHref="#icon-format_quote"></use>
</svg>
</button> </button>
<button <button
class="btn btn-sm text-muted" class="btn btn-sm text-muted"
@ -272,9 +255,7 @@ export class MarkdownTextArea extends Component<
aria-label={i18n.t('list')} aria-label={i18n.t('list')}
onClick={linkEvent(this, this.handleInsertList)} onClick={linkEvent(this, this.handleInsertList)}
> >
<svg class="icon icon-inline"> <Icon icon="list" classes="icon-inline" />
<use xlinkHref="#icon-list"></use>
</svg>
</button> </button>
<button <button
class="btn btn-sm text-muted" class="btn btn-sm text-muted"
@ -282,9 +263,7 @@ export class MarkdownTextArea extends Component<
aria-label={i18n.t('code')} aria-label={i18n.t('code')}
onClick={linkEvent(this, this.handleInsertCode)} onClick={linkEvent(this, this.handleInsertCode)}
> >
<svg class="icon icon-inline"> <Icon icon="code" classes="icon-inline" />
<use xlinkHref="#icon-code"></use>
</svg>
</button> </button>
<button <button
class="btn btn-sm text-muted" class="btn btn-sm text-muted"
@ -292,9 +271,7 @@ export class MarkdownTextArea extends Component<
aria-label={i18n.t('subscript')} aria-label={i18n.t('subscript')}
onClick={linkEvent(this, this.handleInsertSubscript)} onClick={linkEvent(this, this.handleInsertSubscript)}
> >
<svg class="icon icon-inline"> <Icon icon="subscript" classes="icon-inline" />
<use xlinkHref="#icon-subscript"></use>
</svg>
</button> </button>
<button <button
class="btn btn-sm text-muted" class="btn btn-sm text-muted"
@ -302,9 +279,7 @@ export class MarkdownTextArea extends Component<
aria-label={i18n.t('superscript')} aria-label={i18n.t('superscript')}
onClick={linkEvent(this, this.handleInsertSuperscript)} onClick={linkEvent(this, this.handleInsertSuperscript)}
> >
<svg class="icon icon-inline"> <Icon icon="superscript" classes="icon-inline" />
<use xlinkHref="#icon-superscript"></use>
</svg>
</button> </button>
<button <button
class="btn btn-sm text-muted" class="btn btn-sm text-muted"
@ -312,9 +287,7 @@ export class MarkdownTextArea extends Component<
aria-label={i18n.t('spoiler')} aria-label={i18n.t('spoiler')}
onClick={linkEvent(this, this.handleInsertSpoiler)} onClick={linkEvent(this, this.handleInsertSpoiler)}
> >
<svg class="icon icon-inline"> <Icon icon="alert-triangle" classes="icon-inline" />
<use xlinkHref="#icon-alert-triangle"></use>
</svg>
</button> </button>
<a <a
href={markdownHelpUrl} href={markdownHelpUrl}
@ -323,9 +296,7 @@ export class MarkdownTextArea extends Component<
title={i18n.t('formatting_help')} title={i18n.t('formatting_help')}
rel="noopener" rel="noopener"
> >
<svg class="icon icon-inline"> <Icon icon="help-circle" classes="icon-inline" />
<use xlinkHref="#icon-help-circle"></use>
</svg>
</a> </a>
</div> </div>
</div> </div>

View file

@ -34,6 +34,7 @@ import { i18n } from '../i18next';
import { InitialFetchRequest } from 'shared/interfaces'; import { InitialFetchRequest } from 'shared/interfaces';
import { UserListing } from './user-listing'; import { UserListing } from './user-listing';
import { CommunityLink } from './community-link'; import { CommunityLink } from './community-link';
import { Spinner } from './icon';
enum ModlogEnum { enum ModlogEnum {
ModRemovePost, ModRemovePost,
@ -364,10 +365,8 @@ export class Modlog extends Component<any, ModlogState> {
path={this.context.router.route.match.url} path={this.context.router.route.match.url}
/> />
{this.state.loading ? ( {this.state.loading ? (
<h5 class=""> <h5>
<svg class="icon icon-spinner spin"> <Spinner />
<use xlinkHref="#icon-spinner"></use>
</svg>
</h5> </h5>
) : ( ) : (
<div> <div>

View file

@ -2,6 +2,7 @@ import { Component } from 'inferno';
import moment from 'moment'; import moment from 'moment';
import { getMomentLanguage, capitalizeFirstLetter } from '../utils'; import { getMomentLanguage, capitalizeFirstLetter } from '../utils';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { Icon } from './icon';
interface MomentTimeProps { interface MomentTimeProps {
data: { data: {
@ -31,9 +32,7 @@ export class MomentTime extends Component<MomentTimeProps, any> {
)} ${this.format(this.props.data.updated)}`} )} ${this.format(this.props.data.updated)}`}
className="font-italics pointer unselectable" className="font-italics pointer unselectable"
> >
<svg class="icon icon-inline mr-1"> <Icon icon="edit-2" classes="icon-inline mr-1" />
<use xlinkHref="#icon-edit-2"></use>
</svg>
{moment.utc(this.props.data.updated).fromNow(!this.props.showAgo)} {moment.utc(this.props.data.updated).fromNow(!this.props.showAgo)}
</span> </span>
); );

View file

@ -35,6 +35,7 @@ import {
} from '../utils'; } from '../utils';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { PictrsImage } from './pictrs-image'; import { PictrsImage } from './pictrs-image';
import { Icon } from './icon';
interface NavbarProps { interface NavbarProps {
site_res: GetSiteResponse; site_res: GetSiteResponse;
@ -198,9 +199,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
to="/inbox" to="/inbox"
title={i18n.t('inbox')} title={i18n.t('inbox')}
> >
<svg class="icon"> <Icon icon="bell" />
<use xlinkHref="#icon-bell"></use>
</svg>
{this.state.unreadCount > 0 && ( {this.state.unreadCount > 0 && (
<span <span
class="mx-1 badge badge-light" class="mx-1 badge badge-light"
@ -220,9 +219,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
onClick={linkEvent(this, this.expandNavbar)} onClick={linkEvent(this, this.expandNavbar)}
data-tippy-content={i18n.t('expand_here')} data-tippy-content={i18n.t('expand_here')}
> >
<svg class="icon"> <Icon icon="menu" />
<use xlinkHref="#icon-menu"></use>
</svg>
</button> </button>
<div <div
className={`${!this.state.expanded && 'collapse'} navbar-collapse`} className={`${!this.state.expanded && 'collapse'} navbar-collapse`}
@ -264,9 +261,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
title={i18n.t('support_lemmy')} title={i18n.t('support_lemmy')}
href={supportLemmyUrl} href={supportLemmyUrl}
> >
<svg class="icon small"> <Icon icon="beer" classes="small" />
<use xlinkHref="#icon-beer"></use>
</svg>
</a> </a>
</li> </li>
</ul> </ul>
@ -278,9 +273,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
to={`/admin`} to={`/admin`}
title={i18n.t('admin_settings')} title={i18n.t('admin_settings')}
> >
<svg class="icon"> <Icon icon="settings" />
<use xlinkHref="#icon-settings"></use>
</svg>
</Link> </Link>
</li> </li>
)} )}
@ -314,9 +307,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
style="color: var(--gray)" style="color: var(--gray)"
aria-label={i18n.t('search')} aria-label={i18n.t('search')}
> >
<svg class="icon"> <Icon icon="search" />
<use xlinkHref="#icon-search"></use>
</svg>
</button> </button>
</form> </form>
)} )}
@ -329,9 +320,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
to="/inbox" to="/inbox"
title={i18n.t('inbox')} title={i18n.t('inbox')}
> >
<svg class="icon"> <Icon icon="bell" />
<use xlinkHref="#icon-bell"></use>
</svg>
{this.state.unreadCount > 0 && ( {this.state.unreadCount > 0 && (
<span <span
class="ml-1 badge badge-light" class="ml-1 badge badge-light"

View file

@ -19,6 +19,7 @@ import {
} from '../utils'; } from '../utils';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { HtmlTags } from './html-tags'; import { HtmlTags } from './html-tags';
import { Spinner } from './icon';
interface State { interface State {
passwordChangeForm: PasswordChangeForm; passwordChangeForm: PasswordChangeForm;
@ -113,9 +114,7 @@ export class PasswordChange extends Component<any, State> {
<div class="col-sm-10"> <div class="col-sm-10">
<button type="submit" class="btn btn-secondary"> <button type="submit" class="btn btn-secondary">
{this.state.loading ? ( {this.state.loading ? (
<svg class="icon icon-spinner spin"> <Spinner />
<use xlinkHref="#icon-spinner"></use>
</svg>
) : ( ) : (
capitalizeFirstLetter(i18n.t('save')) capitalizeFirstLetter(i18n.t('save'))
)} )}

View file

@ -2,6 +2,7 @@ import { Component, linkEvent } from 'inferno';
import { Prompt } from 'inferno-router'; import { Prompt } from 'inferno-router';
import { PostListings } from './post-listings'; import { PostListings } from './post-listings';
import { MarkdownTextArea } from './markdown-textarea'; import { MarkdownTextArea } from './markdown-textarea';
import { Icon, Spinner } from './icon';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { import {
CreatePost, CreatePost,
@ -194,9 +195,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
} d-inline-block float-right text-muted font-weight-bold`} } d-inline-block float-right text-muted font-weight-bold`}
data-tippy-content={i18n.t('upload_image')} data-tippy-content={i18n.t('upload_image')}
> >
<svg class="icon icon-inline"> <Icon icon="image" classes="icon-inline" />
<use xlinkHref="#icon-image"></use>
</svg>
</label> </label>
<input <input
id="file-upload" id="file-upload"
@ -220,11 +219,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
{i18n.t('archive_link')} {i18n.t('archive_link')}
</a> </a>
)} )}
{this.state.imageLoading && ( {this.state.imageLoading && <Spinner />}
<svg class="icon icon-spinner spin">
<use xlinkHref="#icon-spinner"></use>
</svg>
)}
{isImage(this.state.postForm.url) && ( {isImage(this.state.postForm.url) && (
<img src={this.state.postForm.url} class="img-fluid" alt="" /> <img src={this.state.postForm.url} class="img-fluid" alt="" />
)} )}
@ -343,9 +338,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
class="btn btn-secondary mr-2" class="btn btn-secondary mr-2"
> >
{this.state.loading ? ( {this.state.loading ? (
<svg class="icon icon-spinner spin"> <Spinner />
<use xlinkHref="#icon-spinner"></use>
</svg>
) : this.props.post_view ? ( ) : this.props.post_view ? (
capitalizeFirstLetter(i18n.t('save')) capitalizeFirstLetter(i18n.t('save'))
) : ( ) : (

View file

@ -25,6 +25,7 @@ import { IFramelyCard } from './iframely-card';
import { UserListing } from './user-listing'; import { UserListing } from './user-listing';
import { CommunityLink } from './community-link'; import { CommunityLink } from './community-link';
import { PictrsImage } from './pictrs-image'; import { PictrsImage } from './pictrs-image';
import { Icon } from './icon';
import { import {
md, md,
mdToHtml, mdToHtml,
@ -205,9 +206,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
aria-label={i18n.t('expand_here')} aria-label={i18n.t('expand_here')}
> >
{this.imgThumb(this.getImageSrc())} {this.imgThumb(this.getImageSrc())}
<svg class="icon mini-overlay"> <Icon icon="image" classes="mini-overlay" />
<use xlinkHref="#icon-image"></use>
</svg>
</div> </div>
); );
} else if (post.thumbnail_url) { } else if (post.thumbnail_url) {
@ -220,9 +219,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
title={post.url} title={post.url}
> >
{this.imgThumb(this.getImageSrc())} {this.imgThumb(this.getImageSrc())}
<svg class="icon mini-overlay"> <Icon icon="external-link" classes="mini-overlay" />
<use xlinkHref="#icon-external-link"></use>
</svg>
</a> </a>
); );
} else if (post.url) { } else if (post.url) {
@ -250,9 +247,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
rel="noopener" rel="noopener"
> >
<div class="thumbnail rounded bg-light d-flex justify-content-center"> <div class="thumbnail rounded bg-light d-flex justify-content-center">
<svg class="icon d-flex align-items-center"> <Icon icon="external-link" classes="d-flex align-items-center" />
<use xlinkHref="#icon-external-link"></use>
</svg>
</div> </div>
</a> </a>
); );
@ -265,9 +260,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
title={i18n.t('comments')} title={i18n.t('comments')}
> >
<div class="thumbnail rounded bg-light d-flex justify-content-center"> <div class="thumbnail rounded bg-light d-flex justify-content-center">
<svg class="icon d-flex align-items-center"> <Icon icon="message-square" classes="d-flex align-items-center" />
<use xlinkHref="#icon-message-square"></use>
</svg>
</div> </div>
</Link> </Link>
); );
@ -334,9 +327,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
aria-label={i18n.t('upvote')} aria-label={i18n.t('upvote')}
to={`/post/${post_view.post.id}`} to={`/post/${post_view.post.id}`}
> >
<svg class="mr-1 icon icon-inline"> <Icon icon="book-open" classes="icon-inline mr-1" />
<use xlinkHref="#icon-book-open"></use>
</svg>
</Link> </Link>
</li> </li>
</> </>
@ -356,9 +347,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
data-tippy-content={i18n.t('upvote')} data-tippy-content={i18n.t('upvote')}
aria-label={i18n.t('upvote')} aria-label={i18n.t('upvote')}
> >
<svg class="icon upvote"> <Icon icon="arrow-up1" classes="upvote" />
<use xlinkHref="#icon-arrow-up1"></use>
</svg>
</button> </button>
<div <div
class={`unselectable pointer font-weight-bold text-muted px-1`} class={`unselectable pointer font-weight-bold text-muted px-1`}
@ -375,9 +364,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
data-tippy-content={i18n.t('downvote')} data-tippy-content={i18n.t('downvote')}
aria-label={i18n.t('downvote')} aria-label={i18n.t('downvote')}
> >
<svg class="icon downvote"> <Icon icon="arrow-down1" classes="downvote" />
<use xlinkHref="#icon-arrow-down1"></use>
</svg>
</button> </button>
)} )}
</div> </div>
@ -415,9 +402,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
data-tippy-content={i18n.t('expand_here')} data-tippy-content={i18n.t('expand_here')}
onClick={linkEvent(this, this.handleImageExpandClick)} onClick={linkEvent(this, this.handleImageExpandClick)}
> >
<svg class="icon icon-inline"> <Icon icon="plus-square" classes="icon-inline" />
<use xlinkHref="#icon-plus-square"></use>
</svg>
</span> </span>
) : ( ) : (
<span> <span>
@ -425,9 +410,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
class="text-monospace unselectable pointer ml-2 text-muted small" class="text-monospace unselectable pointer ml-2 text-muted small"
onClick={linkEvent(this, this.handleImageExpandClick)} onClick={linkEvent(this, this.handleImageExpandClick)}
> >
<svg class="icon icon-inline"> <Icon icon="minus-square" classes="icon-inline" />
<use xlinkHref="#icon-minus-square"></use>
</svg>
</span> </span>
<div> <div>
<span <span
@ -449,9 +432,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
className="unselectable pointer ml-2 text-muted font-italic" className="unselectable pointer ml-2 text-muted font-italic"
data-tippy-content={i18n.t('deleted')} data-tippy-content={i18n.t('deleted')}
> >
<svg class={`icon icon-inline text-danger`}> <Icon icon="trash" classes="icon-inline text-danger" />
<use xlinkHref="#icon-trash"></use>
</svg>
</small> </small>
)} )}
{post.locked && ( {post.locked && (
@ -459,9 +440,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
className="unselectable pointer ml-2 text-muted font-italic" className="unselectable pointer ml-2 text-muted font-italic"
data-tippy-content={i18n.t('locked')} data-tippy-content={i18n.t('locked')}
> >
<svg class={`icon icon-inline text-danger`}> <Icon icon="lock" classes="icon-inline text-danger" />
<use xlinkHref="#icon-lock"></use>
</svg>
</small> </small>
)} )}
{post.stickied && ( {post.stickied && (
@ -469,9 +448,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
className="unselectable pointer ml-2 text-muted font-italic" className="unselectable pointer ml-2 text-muted font-italic"
data-tippy-content={i18n.t('stickied')} data-tippy-content={i18n.t('stickied')}
> >
<svg class={`icon icon-inline text-primary`}> <Icon icon="pin" classes="icon-inline text-primary" />
<use xlinkHref="#icon-pin"></use>
</svg>
</small> </small>
)} )}
{post.nsfw && ( {post.nsfw && (
@ -496,9 +473,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
})} })}
to={`/post/${post_view.post.id}`} to={`/post/${post_view.post.id}`}
> >
<svg class="mr-1 icon icon-inline"> <Icon icon="message-square" classes="icon-inline mr-1" />
<use xlinkHref="#icon-message-square"></use>
</svg>
{i18n.t('number_of_comments', { {i18n.t('number_of_comments', {
count: post_view.counts.comments, count: post_view.counts.comments,
})} })}
@ -513,9 +488,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
aria-label={i18n.t('downvote')} aria-label={i18n.t('downvote')}
> >
<small> <small>
<svg class="icon icon-inline mr-1"> <Icon icon="arrow-down1" classes="icon-inline mr-1" />
<use xlinkHref="#icon-arrow-down1"></use>
</svg>
<span>{this.state.downvotes}</span> <span>{this.state.downvotes}</span>
</small> </small>
</button> </button>
@ -530,13 +503,10 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
aria-label={post_view.saved ? i18n.t('unsave') : i18n.t('save')} aria-label={post_view.saved ? i18n.t('unsave') : i18n.t('save')}
> >
<small> <small>
<svg <Icon
class={`icon icon-inline ${ icon="star"
post_view.saved && 'text-warning' classes={`icon-inline ${post_view.saved && 'text-warning'}`}
}`} />
>
<use xlinkHref="#icon-star"></use>
</svg>
</small> </small>
</button> </button>
)} )}
@ -555,9 +525,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
onClick={linkEvent(this, this.handlePostLike)} onClick={linkEvent(this, this.handlePostLike)}
aria-label={i18n.t('upvote')} aria-label={i18n.t('upvote')}
> >
<svg class="small icon icon-inline mr-2"> <Icon icon="arrow-up1" classes="icon-inline small mr-2" />
<use xlinkHref="#icon-arrow-up1"></use>
</svg>
{this.state.upvotes} {this.state.upvotes}
</button> </button>
{this.props.enableDownvotes && ( {this.props.enableDownvotes && (
@ -569,9 +537,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
data-tippy-content={this.pointsTippy} data-tippy-content={this.pointsTippy}
aria-label={i18n.t('downvote')} aria-label={i18n.t('downvote')}
> >
<svg class="small icon icon-inline mr-2"> <Icon icon="arrow-down1" classes="icon-inline small mr-2" />
<use xlinkHref="#icon-arrow-down1"></use>
</svg>
{this.state.downvotes !== 0 && ( {this.state.downvotes !== 0 && (
<span>{this.state.downvotes}</span> <span>{this.state.downvotes}</span>
)} )}
@ -586,11 +552,10 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
post_view.saved ? i18n.t('unsave') : i18n.t('save') post_view.saved ? i18n.t('unsave') : i18n.t('save')
} }
> >
<svg <Icon
class={`icon icon-inline ${post_view.saved && 'text-warning'}`} icon="star"
> classes={`icon-inline ${post_view.saved && 'text-warning'}`}
<use xlinkHref="#icon-star"></use> />
</svg>
</button> </button>
{!this.state.showMoreMobile && this.props.showBody && ( {!this.state.showMoreMobile && this.props.showBody && (
@ -600,9 +565,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
aria-label={i18n.t('more')} aria-label={i18n.t('more')}
data-tippy-content={i18n.t('more')} data-tippy-content={i18n.t('more')}
> >
<svg class="icon icon-inline"> <Icon icon="more-vertical" classes="icon-inline" />
<use xlinkHref="#icon-more-vertical"></use>
</svg>
</button> </button>
)} )}
{this.state.showMoreMobile && this.postActions(mobile)} {this.state.showMoreMobile && this.postActions(mobile)}
@ -655,13 +618,10 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
post_view.saved ? i18n.t('unsave') : i18n.t('save') post_view.saved ? i18n.t('unsave') : i18n.t('save')
} }
> >
<svg <Icon
class={`icon icon-inline ${ icon="star"
post_view.saved && 'text-warning' classes={`icon-inline ${post_view.saved && 'text-warning'}`}
}`} />
>
<use xlinkHref="#icon-star"></use>
</svg>
</button> </button>
)} )}
<Link <Link
@ -669,9 +629,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
to={`/create_post${this.crossPostParams}`} to={`/create_post${this.crossPostParams}`}
title={i18n.t('cross_post')} title={i18n.t('cross_post')}
> >
<svg class="icon icon-inline"> <Icon icon="copy" classes="icon-inline" />
<use xlinkHref="#icon-copy"></use>
</svg>
</Link> </Link>
</> </>
)} )}
@ -683,9 +641,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
data-tippy-content={i18n.t('edit')} data-tippy-content={i18n.t('edit')}
aria-label={i18n.t('edit')} aria-label={i18n.t('edit')}
> >
<svg class="icon icon-inline"> <Icon icon="edit" classes="icon-inline" />
<use xlinkHref="#icon-edit"></use>
</svg>
</button> </button>
<button <button
class="btn btn-link btn-animate text-muted py-0" class="btn btn-link btn-animate text-muted py-0"
@ -697,13 +653,12 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
!post_view.post.deleted ? i18n.t('delete') : i18n.t('restore') !post_view.post.deleted ? i18n.t('delete') : i18n.t('restore')
} }
> >
<svg <Icon
class={`icon icon-inline ${ icon="trash"
classes={`icon-inline ${
post_view.post.deleted && 'text-danger' post_view.post.deleted && 'text-danger'
}`} }`}
> />
<use xlinkHref="#icon-trash"></use>
</svg>
</button> </button>
</> </>
)} )}
@ -715,9 +670,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
data-tippy-content={i18n.t('more')} data-tippy-content={i18n.t('more')}
aria-label={i18n.t('more')} aria-label={i18n.t('more')}
> >
<svg class="icon icon-inline"> <Icon icon="more-vertical" classes="icon-inline" />
<use xlinkHref="#icon-more-vertical"></use>
</svg>
</button> </button>
) : ( ) : (
<> <>
@ -728,13 +681,12 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
data-tippy-content={i18n.t('view_source')} data-tippy-content={i18n.t('view_source')}
aria-label={i18n.t('view_source')} aria-label={i18n.t('view_source')}
> >
<svg <Icon
class={`icon icon-inline ${ icon="file-text"
classes={`icon-inline ${
this.state.viewSource && 'text-success' this.state.viewSource && 'text-success'
}`} }`}
> />
<use xlinkHref="#icon-file-text"></use>
</svg>
</button> </button>
)} )}
{this.canModOnSelf && ( {this.canModOnSelf && (
@ -749,13 +701,12 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
post_view.post.locked ? i18n.t('unlock') : i18n.t('lock') post_view.post.locked ? i18n.t('unlock') : i18n.t('lock')
} }
> >
<svg <Icon
class={`icon icon-inline ${ icon="lock"
classes={`icon-inline ${
post_view.post.locked && 'text-danger' post_view.post.locked && 'text-danger'
}`} }`}
> />
<use xlinkHref="#icon-lock"></use>
</svg>
</button> </button>
<button <button
class="btn btn-link btn-animate text-muted py-0" class="btn btn-link btn-animate text-muted py-0"
@ -771,13 +722,12 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
: i18n.t('sticky') : i18n.t('sticky')
} }
> >
<svg <Icon
class={`icon icon-inline ${ icon="pin"
classes={`icon-inline ${
post_view.post.stickied && 'text-success' post_view.post.stickied && 'text-success'
}`} }`}
> />
<use xlinkHref="#icon-pin"></use>
</svg>
</button> </button>
</> </>
)} )}

View file

@ -1,5 +1,6 @@
import { Component, linkEvent } from 'inferno'; import { Component, linkEvent } from 'inferno';
import { HtmlTags } from './html-tags'; import { HtmlTags } from './html-tags';
import { Spinner } from './icon';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { import {
UserOperation, UserOperation,
@ -256,9 +257,7 @@ export class Post extends Component<any, PostState> {
<div class="container"> <div class="container">
{this.state.loading ? ( {this.state.loading ? (
<h5> <h5>
<svg class="icon icon-spinner spin"> <Spinner />
<use xlinkHref="#icon-spinner"></use>
</svg>
</h5> </h5>
) : ( ) : (
<div class="row"> <div class="row">

View file

@ -23,6 +23,7 @@ import {
} from '../utils'; } from '../utils';
import { UserListing } from './user-listing'; import { UserListing } from './user-listing';
import { MarkdownTextArea } from './markdown-textarea'; import { MarkdownTextArea } from './markdown-textarea';
import { Icon, Spinner } from './icon';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { T } from 'inferno-i18next'; import { T } from 'inferno-i18next';
@ -121,9 +122,7 @@ export class PrivateMessageForm extends Component<
data-tippy-content={i18n.t('disclaimer')} data-tippy-content={i18n.t('disclaimer')}
aria-label={i18n.t('disclaimer')} aria-label={i18n.t('disclaimer')}
> >
<svg class={`icon icon-inline`}> <Icon icon="alert-triangle" classes="icon-inline" />
<use xlinkHref="#icon-alert-triangle"></use>
</svg>
</span> </span>
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
@ -161,9 +160,7 @@ export class PrivateMessageForm extends Component<
disabled={this.state.loading} disabled={this.state.loading}
> >
{this.state.loading ? ( {this.state.loading ? (
<svg class="icon icon-spinner spin"> <Spinner />
<use xlinkHref="#icon-spinner"></use>
</svg>
) : this.props.privateMessage ? ( ) : this.props.privateMessage ? (
capitalizeFirstLetter(i18n.t('save')) capitalizeFirstLetter(i18n.t('save'))
) : ( ) : (

View file

@ -10,6 +10,7 @@ import { authField, mdToHtml, toast, wsClient } from '../utils';
import { MomentTime } from './moment-time'; import { MomentTime } from './moment-time';
import { PrivateMessageForm } from './private-message-form'; import { PrivateMessageForm } from './private-message-form';
import { UserListing } from './user-listing'; import { UserListing } from './user-listing';
import { Icon } from './icon';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
interface PrivateMessageState { interface PrivateMessageState {
@ -82,13 +83,9 @@ export class PrivateMessage extends Component<
onClick={linkEvent(this, this.handleMessageCollapse)} onClick={linkEvent(this, this.handleMessageCollapse)}
> >
{this.state.collapsed ? ( {this.state.collapsed ? (
<svg class="icon icon-inline"> <Icon icon="plus-square" classes="icon-inline" />
<use xlinkHref="#icon-plus-square"></use>
</svg>
) : ( ) : (
<svg class="icon icon-inline"> <Icon icon="minus-square" classes="icon-inline" />
<use xlinkHref="#icon-minus-square"></use>
</svg>
)} )}
</div> </div>
</li> </li>
@ -130,13 +127,12 @@ export class PrivateMessage extends Component<
: i18n.t('mark_as_read') : i18n.t('mark_as_read')
} }
> >
<svg <Icon
class={`icon icon-inline ${ icon="check"
classes={`icon-inline ${
message_view.private_message.read && 'text-success' message_view.private_message.read && 'text-success'
}`} }`}
> />
<use xlinkHref="#icon-check"></use>
</svg>
</button> </button>
</li> </li>
<li className="list-inline-item"> <li className="list-inline-item">
@ -146,9 +142,7 @@ export class PrivateMessage extends Component<
data-tippy-content={i18n.t('reply')} data-tippy-content={i18n.t('reply')}
aria-label={i18n.t('reply')} aria-label={i18n.t('reply')}
> >
<svg class="icon icon-inline"> <Icon icon="reply1" classes="icon-inline" />
<use xlinkHref="#icon-reply1"></use>
</svg>
</button> </button>
</li> </li>
</> </>
@ -162,9 +156,7 @@ export class PrivateMessage extends Component<
data-tippy-content={i18n.t('edit')} data-tippy-content={i18n.t('edit')}
aria-label={i18n.t('edit')} aria-label={i18n.t('edit')}
> >
<svg class="icon icon-inline"> <Icon icon="edit" classes="icon-inline" />
<use xlinkHref="#icon-edit"></use>
</svg>
</button> </button>
</li> </li>
<li className="list-inline-item"> <li className="list-inline-item">
@ -182,14 +174,13 @@ export class PrivateMessage extends Component<
: i18n.t('restore') : i18n.t('restore')
} }
> >
<svg <Icon
class={`icon icon-inline ${ icon="trash"
classes={`icon-inline ${
message_view.private_message.deleted && message_view.private_message.deleted &&
'text-danger' 'text-danger'
}`} }`}
> />
<use xlinkHref="#icon-trash"></use>
</svg>
</button> </button>
</li> </li>
</> </>
@ -201,13 +192,12 @@ export class PrivateMessage extends Component<
data-tippy-content={i18n.t('view_source')} data-tippy-content={i18n.t('view_source')}
aria-label={i18n.t('view_source')} aria-label={i18n.t('view_source')}
> >
<svg <Icon
class={`icon icon-inline ${ icon="file-text"
classes={`icon-inline ${
this.state.viewSource && 'text-success' this.state.viewSource && 'text-success'
}`} }`}
> />
<use xlinkHref="#icon-file-text"></use>
</svg>
</button> </button>
</li> </li>
</ul> </ul>

View file

@ -35,6 +35,7 @@ import {
} from '../utils'; } from '../utils';
import { PostListing } from './post-listing'; import { PostListing } from './post-listing';
import { HtmlTags } from './html-tags'; import { HtmlTags } from './html-tags';
import { Spinner } from './icon';
import { UserListing } from './user-listing'; import { UserListing } from './user-listing';
import { CommunityLink } from './community-link'; import { CommunityLink } from './community-link';
import { SortSelect } from './sort-select'; import { SortSelect } from './sort-select';
@ -214,18 +215,8 @@ export class Search extends Component<any, SearchState> {
required required
minLength={3} minLength={3}
/> />
<button <button type="submit" class="btn btn-secondary mr-2 mb-2">
type="submit" {this.state.loading ? <Spinner /> : <span>{i18n.t('search')}</span>}
class="btn btn-secondary mr-2 mb-2"
aria-label={i18n.t('search')}
>
{this.state.loading ? (
<svg class="icon icon-spinner spin">
<use xlinkHref="#icon-spinner"></use>
</svg>
) : (
<span>{i18n.t('search')}</span>
)}
</button> </button>
</form> </form>
); );

View file

@ -6,6 +6,7 @@ import { Register, LoginResponse, UserOperation } from 'lemmy-js-client';
import { WebSocketService, UserService } from '../services'; import { WebSocketService, UserService } from '../services';
import { wsUserOp, wsJsonToRes, toast, wsClient } from '../utils'; import { wsUserOp, wsJsonToRes, toast, wsClient } from '../utils';
import { SiteForm } from './site-form'; import { SiteForm } from './site-form';
import { Spinner } from './icon';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
interface State { interface State {
@ -143,13 +144,7 @@ export class Setup extends Component<any, State> {
<div class="form-group row"> <div class="form-group row">
<div class="col-sm-10"> <div class="col-sm-10">
<button type="submit" class="btn btn-secondary"> <button type="submit" class="btn btn-secondary">
{this.state.userLoading ? ( {this.state.userLoading ? <Spinner /> : i18n.t('sign_up')}
<svg class="icon icon-spinner spin">
<use xlinkHref="#icon-spinner"></use>
</svg>
) : (
i18n.t('sign_up')
)}
</button> </button>
</div> </div>
</div> </div>

View file

@ -16,6 +16,7 @@ import { CommunityForm } from './community-form';
import { UserListing } from './user-listing'; import { UserListing } from './user-listing';
import { CommunityLink } from './community-link'; import { CommunityLink } from './community-link';
import { BannerIconHeader } from './banner-icon-header'; import { BannerIconHeader } from './banner-icon-header';
import { Icon } from './icon';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
interface SidebarProps { interface SidebarProps {
@ -108,9 +109,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
href="#" href="#"
onClick={linkEvent(community.id, this.handleUnsubscribe)} onClick={linkEvent(community.id, this.handleUnsubscribe)}
> >
<svg class="text-success mr-1 icon icon-inline"> <Icon icon="check" classes="icon-inline text-success mr-1" />
<use xlinkHref="#icon-check"></use>
</svg>
{i18n.t('joined')} {i18n.t('joined')}
</a> </a>
)} )}
@ -305,9 +304,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
data-tippy-content={i18n.t('edit')} data-tippy-content={i18n.t('edit')}
aria-label={i18n.t('edit')} aria-label={i18n.t('edit')}
> >
<svg class="icon icon-inline"> <Icon icon="edit" classes="icon-inline" />
<use xlinkHref="#icon-edit"></use>
</svg>
</span> </span>
</li> </li>
{!this.amCreator && {!this.amCreator &&
@ -368,13 +365,12 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
: i18n.t('restore') : i18n.t('restore')
} }
> >
<svg <Icon
class={`icon icon-inline ${ icon="trash"
classes={`icon-inline ${
community_view.community.deleted && 'text-danger' community_view.community.deleted && 'text-danger'
}`} }`}
> />
<use xlinkHref="#icon-trash"></use>
</svg>
</span> </span>
</li> </li>
)} )}

View file

@ -1,6 +1,7 @@
import { Component, linkEvent } from 'inferno'; import { Component, linkEvent } from 'inferno';
import { Prompt } from 'inferno-router'; import { Prompt } from 'inferno-router';
import { MarkdownTextArea } from './markdown-textarea'; import { MarkdownTextArea } from './markdown-textarea';
import { Spinner } from './icon';
import { ImageUploadForm } from './image-upload-form'; import { ImageUploadForm } from './image-upload-form';
import { Site, EditSite } from 'lemmy-js-client'; import { Site, EditSite } from 'lemmy-js-client';
import { WebSocketService } from '../services'; import { WebSocketService } from '../services';
@ -220,9 +221,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
disabled={this.state.loading} disabled={this.state.loading}
> >
{this.state.loading ? ( {this.state.loading ? (
<svg class="icon icon-spinner spin"> <Spinner />
<use xlinkHref="#icon-spinner"></use>
</svg>
) : this.props.site ? ( ) : this.props.site ? (
capitalizeFirstLetter(i18n.t('save')) capitalizeFirstLetter(i18n.t('save'))
) : ( ) : (

View file

@ -1,6 +1,7 @@
import { Component, linkEvent } from 'inferno'; import { Component, linkEvent } from 'inferno';
import { SortType } from 'lemmy-js-client'; import { SortType } from 'lemmy-js-client';
import { sortingHelpUrl, randomStr } from '../utils'; import { sortingHelpUrl, randomStr } from '../utils';
import { Icon } from './icon';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
interface SortSelectProps { interface SortSelectProps {
@ -42,7 +43,9 @@ export class SortSelect extends Component<SortSelectProps, SortSelectState> {
class="custom-select w-auto mr-2 mb-2" class="custom-select w-auto mr-2 mb-2"
aria-label={i18n.t('sort_type')} aria-label={i18n.t('sort_type')}
> >
<option disabled aria-hidden="true">{i18n.t('sort_type')}</option> <option disabled aria-hidden="true">
{i18n.t('sort_type')}
</option>
{!this.props.hideHot && [ {!this.props.hideHot && [
<option value={SortType.Hot}>{i18n.t('hot')}</option>, <option value={SortType.Hot}>{i18n.t('hot')}</option>,
<option value={SortType.Active}>{i18n.t('active')}</option>, <option value={SortType.Active}>{i18n.t('active')}</option>,
@ -53,7 +56,9 @@ export class SortSelect extends Component<SortSelectProps, SortSelectState> {
{i18n.t('most_comments')} {i18n.t('most_comments')}
</option> </option>
)} )}
<option disabled aria-hidden="true"></option> <option disabled aria-hidden="true">
</option>
<option value={SortType.TopDay}>{i18n.t('top_day')}</option> <option value={SortType.TopDay}>{i18n.t('top_day')}</option>
<option value={SortType.TopWeek}>{i18n.t('top_week')}</option> <option value={SortType.TopWeek}>{i18n.t('top_week')}</option>
<option value={SortType.TopMonth}>{i18n.t('top_month')}</option> <option value={SortType.TopMonth}>{i18n.t('top_month')}</option>
@ -67,9 +72,7 @@ export class SortSelect extends Component<SortSelectProps, SortSelectState> {
rel="noopener" rel="noopener"
title={i18n.t('sorting_help')} title={i18n.t('sorting_help')}
> >
<svg class={`icon icon-inline`}> <Icon icon="help-circle" classes="icon-inline" />
<use xlinkHref="#icon-help-circle"></use>
</svg>
</a> </a>
</> </>
); );

View file

@ -57,6 +57,7 @@ import { i18n } from '../i18next';
import moment from 'moment'; import moment from 'moment';
import { UserDetails } from './user-details'; import { UserDetails } from './user-details';
import { MarkdownTextArea } from './markdown-textarea'; import { MarkdownTextArea } from './markdown-textarea';
import { Icon, Spinner } from './icon';
import { ImageUploadForm } from './image-upload-form'; import { ImageUploadForm } from './image-upload-form';
import { BannerIconHeader } from './banner-icon-header'; import { BannerIconHeader } from './banner-icon-header';
import { CommunityLink } from './community-link'; import { CommunityLink } from './community-link';
@ -272,9 +273,7 @@ export class User extends Component<any, UserState> {
<div class="container"> <div class="container">
{this.state.loading ? ( {this.state.loading ? (
<h5> <h5>
<svg class="icon icon-spinner spin"> <Spinner />
<use xlinkHref="#icon-spinner"></use>
</svg>
</h5> </h5>
) : ( ) : (
<div class="row"> <div class="row">
@ -393,9 +392,7 @@ export class User extends Component<any, UserState> {
rel="noopener" rel="noopener"
title="RSS" title="RSS"
> >
<svg class="icon mx-2 text-muted small"> <Icon icon="rss" classes="text-muted small mx-2" />
<use xlinkHref="#icon-rss"></use>
</svg>
</a> </a>
</div> </div>
); );
@ -485,9 +482,7 @@ export class User extends Component<any, UserState> {
<MomentTime data={uv.user} showAgo ignoreUpdated /> <MomentTime data={uv.user} showAgo ignoreUpdated />
</div> </div>
<div className="d-flex align-items-center text-muted mb-2"> <div className="d-flex align-items-center text-muted mb-2">
<svg class="icon"> <Icon icon="cake" />
<use xlinkHref="#icon-cake"></use>
</svg>
<span className="ml-2"> <span className="ml-2">
{i18n.t('cake_day_title')}{' '} {i18n.t('cake_day_title')}{' '}
{moment.utc(uv.user.published).local().format('MMM DD, YYYY')} {moment.utc(uv.user.published).local().format('MMM DD, YYYY')}
@ -787,9 +782,7 @@ export class User extends Component<any, UserState> {
<div class="form-group"> <div class="form-group">
<button type="submit" class="btn btn-block btn-secondary mr-4"> <button type="submit" class="btn btn-block btn-secondary mr-4">
{this.state.userSettingsLoading ? ( {this.state.userSettingsLoading ? (
<svg class="icon icon-spinner spin"> <Spinner />
<use xlinkHref="#icon-spinner"></use>
</svg>
) : ( ) : (
capitalizeFirstLetter(i18n.t('save')) capitalizeFirstLetter(i18n.t('save'))
)} )}
@ -827,9 +820,7 @@ export class User extends Component<any, UserState> {
onClick={linkEvent(this, this.handleDeleteAccount)} onClick={linkEvent(this, this.handleDeleteAccount)}
> >
{this.state.deleteAccountLoading ? ( {this.state.deleteAccountLoading ? (
<svg class="icon icon-spinner spin"> <Spinner />
<use xlinkHref="#icon-spinner"></use>
</svg>
) : ( ) : (
capitalizeFirstLetter(i18n.t('delete')) capitalizeFirstLetter(i18n.t('delete'))
)} )}