From: Dessalines <tyhou13@gmx.com> Date: Thu, 11 Feb 2021 20:35:27 +0000 (-0500) Subject: Adding an icon component. Fixes #172 X-Git-Url: http://these/git/%7BpictrsAvatarThumbnail%28user.avatar%29%7D?a=commitdiff_plain;h=2a85b93c589b33738d31dcdf124fba6b3e809e06;p=lemmy-ui.git Adding an icon component. Fixes #172 --- diff --git a/src/shared/components/admin-settings.tsx b/src/shared/components/admin-settings.tsx index 6b985a4..596c83c 100644 --- a/src/shared/components/admin-settings.tsx +++ b/src/shared/components/admin-settings.tsx @@ -25,6 +25,7 @@ import autosize from 'autosize'; import { SiteForm } from './site-form'; import { UserListing } from './user-listing'; import { HtmlTags } from './html-tags'; +import { Spinner } from './icon'; import { i18n } from '../i18next'; import { InitialFetchRequest } from 'shared/interfaces'; @@ -109,9 +110,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> { /> {this.state.loading ? ( <h5> - <svg class="icon icon-spinner spin"> - <use xlinkHref="#icon-spinner"></use> - </svg> + <Spinner /> </h5> ) : ( <div class="row"> @@ -185,9 +184,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> { <div class="col-12"> <button type="submit" class="btn btn-secondary mr-2"> {this.state.siteConfigLoading ? ( - <svg class="icon icon-spinner spin"> - <use xlinkHref="#icon-spinner"></use> - </svg> + <Spinner /> ) : ( capitalizeFirstLetter(i18n.t('save')) )} diff --git a/src/shared/components/cake-day.tsx b/src/shared/components/cake-day.tsx index f28be33..b96f02e 100644 --- a/src/shared/components/cake-day.tsx +++ b/src/shared/components/cake-day.tsx @@ -1,5 +1,6 @@ import { Component } from 'inferno'; import { i18n } from '../i18next'; +import { Icon } from './icon'; interface CakeDayProps { creatorName: string; @@ -12,9 +13,7 @@ export class CakeDay extends Component<CakeDayProps, any> { className={`mx-2 d-inline-block unselectable pointer`} data-tippy-content={this.cakeDayTippy()} > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-cake"></use> - </svg> + <Icon icon="cake" classes="icon-inline" /> </div> ); } diff --git a/src/shared/components/comment-form.tsx b/src/shared/components/comment-form.tsx index 53e7934..4142048 100644 --- a/src/shared/components/comment-form.tsx +++ b/src/shared/components/comment-form.tsx @@ -20,6 +20,7 @@ import { WebSocketService, UserService } from '../services'; import { i18n } from '../i18next'; import { T } from 'inferno-i18next'; import { MarkdownTextArea } from './markdown-textarea'; +import { Icon } from './icon'; interface CommentFormProps { postId?: number; @@ -84,9 +85,7 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> { /> ) : ( <div class="alert alert-light" role="alert"> - <svg class="icon icon-inline mr-2"> - <use xlinkHref="#icon-alert-triangle"></use> - </svg> + <Icon icon="alert-triangle" classes="icon-inline mr-2" /> <T i18nKey="must_login" class="d-inline"> # <Link className="alert-link" to="/login"> diff --git a/src/shared/components/comment-node.tsx b/src/shared/components/comment-node.tsx index bf57a59..ab190ad 100644 --- a/src/shared/components/comment-node.tsx +++ b/src/shared/components/comment-node.tsx @@ -36,6 +36,7 @@ import { CommentForm } from './comment-form'; import { CommentNodes } from './comment-nodes'; import { UserListing } from './user-listing'; import { CommunityLink } from './community-link'; +import { Icon, Spinner } from './icon'; import { i18n } from '../i18next'; interface CommentNodeState { @@ -253,13 +254,12 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { {this.state.readLoading ? ( this.loadingIcon ) : ( - <svg - class={`icon icon-inline ${ + <Icon + icon="check" + classes={`icon-inline ${ this.commentOrMentionRead && 'text-success' }`} - > - <use xlinkHref="#icon-check"></use> - </svg> + /> )} </button> )} @@ -273,9 +273,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { data-tippy-content={i18n.t('upvote')} aria-label={i18n.t('upvote')} > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-arrow-up1"></use> - </svg> + <Icon icon="arrow-up1" classes="icon-inline" /> {this.state.upvotes !== this.state.score && ( <span class="ml-1">{this.state.upvotes}</span> )} @@ -291,9 +289,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { data-tippy-content={i18n.t('downvote')} aria-label={i18n.t('downvote')} > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-arrow-down1"></use> - </svg> + <Icon icon="arrow-down1" classes="icon-inline" /> {this.state.upvotes !== this.state.score && ( <span class="ml-1">{this.state.downvotes}</span> )} @@ -305,9 +301,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { data-tippy-content={i18n.t('reply')} aria-label={i18n.t('reply')} > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-reply1"></use> - </svg> + <Icon icon="reply1" classes="icon-inline" /> </button> {!this.state.showAdvanced ? ( <button @@ -316,9 +310,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { data-tippy-content={i18n.t('more')} aria-label={i18n.t('more')} > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-more-vertical"></use> - </svg> + <Icon icon="more-vertical" classes="icon-inline" /> </button> ) : ( <> @@ -329,9 +321,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { to={`/create_private_message/recipient/${cv.creator.id}`} title={i18n.t('message').toLowerCase()} > - <svg class="icon"> - <use xlinkHref="#icon-mail"></use> - </svg> + <Icon icon="mail" /> </Link> </button> )} @@ -352,13 +342,12 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { {this.state.saveLoading ? ( this.loadingIcon ) : ( - <svg - class={`icon icon-inline ${ + <Icon + icon="star" + classes={`icon-inline ${ cv.saved && 'text-warning' }`} - > - <use xlinkHref="#icon-star"></use> - </svg> + /> )} </button> <button @@ -367,13 +356,12 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { data-tippy-content={i18n.t('view_source')} aria-label={i18n.t('view_source')} > - <svg - class={`icon icon-inline ${ + <Icon + icon="file-text" + classes={`icon-inline ${ this.state.viewSource && 'text-success' }`} - > - <use xlinkHref="#icon-file-text"></use> - </svg> + /> </button> {this.myComment && ( <> @@ -383,9 +371,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { data-tippy-content={i18n.t('edit')} aria-label={i18n.t('edit')} > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-edit"></use> - </svg> + <Icon icon="edit" classes="icon-inline" /> </button> <button class="btn btn-link btn-animate text-muted" @@ -404,13 +390,12 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { : i18n.t('restore') } > - <svg - class={`icon icon-inline ${ + <Icon + icon="trash" + classes={`icon-inline ${ cv.comment.deleted && 'text-danger' }`} - > - <use xlinkHref="#icon-trash"></use> - </svg> + /> </button> </> )} @@ -760,19 +745,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { to={`/post/${cv.post.id}/comment/${cv.comment.id}`} title={this.props.showContext ? i18n.t('show_context') : i18n.t('link')} > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-link"></use> - </svg> + <Icon icon="link" classes="icon-inline" /> </Link> ); } get loadingIcon() { - return ( - <svg class="icon icon-spinner spin"> - <use xlinkHref="#icon-spinner"></use> - </svg> - ); + return <Spinner />; } get myComment(): boolean { diff --git a/src/shared/components/communities.tsx b/src/shared/components/communities.tsx index e90c09d..e9fb81c 100644 --- a/src/shared/components/communities.tsx +++ b/src/shared/components/communities.tsx @@ -26,6 +26,7 @@ import { setOptionalAuth, } from '../utils'; import { CommunityLink } from './community-link'; +import { Spinner } from './icon'; import { i18n } from '../i18next'; import { InitialFetchRequest } from 'shared/interfaces'; @@ -104,10 +105,8 @@ export class Communities extends Component<any, CommunitiesState> { path={this.context.router.route.match.url} /> {this.state.loading ? ( - <h5 class=""> - <svg class="icon icon-spinner spin"> - <use xlinkHref="#icon-spinner"></use> - </svg> + <h5> + <Spinner /> </h5> ) : ( <div> diff --git a/src/shared/components/community-form.tsx b/src/shared/components/community-form.tsx index df4b171..7852233 100644 --- a/src/shared/components/community-form.tsx +++ b/src/shared/components/community-form.tsx @@ -24,6 +24,7 @@ import { i18n } from '../i18next'; import { MarkdownTextArea } from './markdown-textarea'; import { ImageUploadForm } from './image-upload-form'; +import { Icon, Spinner } from './icon'; interface CommunityFormProps { 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" data-tippy-content={i18n.t('name_explain')} > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-help-circle"></use> - </svg> + <Icon icon="help-circle" classes="icon-inline" /> </span> </label> <div class="col-12"> @@ -160,9 +159,7 @@ export class CommunityForm extends Component< class="pointer unselectable ml-2 text-muted" data-tippy-content={i18n.t('display_name_explain')} > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-help-circle"></use> - </svg> + <Icon icon="help-circle" classes="icon-inline" /> </span> </label> <div class="col-12"> @@ -252,9 +249,7 @@ export class CommunityForm extends Component< disabled={this.state.loading} > {this.state.loading ? ( - <svg class="icon icon-spinner spin"> - <use xlinkHref="#icon-spinner"></use> - </svg> + <Spinner /> ) : this.props.community_view ? ( capitalizeFirstLetter(i18n.t('save')) ) : ( diff --git a/src/shared/components/community.tsx b/src/shared/components/community.tsx index 86ac243..156a9e9 100644 --- a/src/shared/components/community.tsx +++ b/src/shared/components/community.tsx @@ -31,6 +31,7 @@ import { DataTypeSelect } from './data-type-select'; import { Sidebar } from './sidebar'; import { CommunityLink } from './community-link'; import { BannerIconHeader } from './banner-icon-header'; +import { Icon, Spinner } from './icon'; import { wsJsonToRes, fetchLimit, @@ -244,9 +245,7 @@ export class Community extends Component<any, State> { <div class="container"> {this.state.communityLoading ? ( <h5> - <svg class="icon icon-spinner spin"> - <use xlinkHref="#icon-spinner"></use> - </svg> + <Spinner /> </h5> ) : ( <div class="row"> @@ -283,9 +282,7 @@ export class Community extends Component<any, State> { return this.state.dataType == DataType.Post ? ( this.state.postsLoading ? ( <h5> - <svg class="icon icon-spinner spin"> - <use xlinkHref="#icon-spinner"></use> - </svg> + <Spinner /> </h5> ) : ( <PostListings @@ -297,9 +294,7 @@ export class Community extends Component<any, State> { ) ) : this.state.commentsLoading ? ( <h5> - <svg class="icon icon-spinner spin"> - <use xlinkHref="#icon-spinner"></use> - </svg> + <Spinner /> </h5> ) : ( <CommentNodes @@ -350,9 +345,7 @@ export class Community extends Component<any, State> { title="RSS" rel="noopener" > - <svg class="icon text-muted small"> - <use xlinkHref="#icon-rss">#</use> - </svg> + <Icon icon="rss" classes="text-muted small" /> </a> </div> ); diff --git a/src/shared/components/create-community.tsx b/src/shared/components/create-community.tsx index aab5480..85b3547 100644 --- a/src/shared/components/create-community.tsx +++ b/src/shared/components/create-community.tsx @@ -2,6 +2,7 @@ import { Component } from 'inferno'; import { Subscription } from 'rxjs'; import { CommunityForm } from './community-form'; import { HtmlTags } from './html-tags'; +import { Spinner } from './icon'; import { CommunityView, UserOperation, @@ -77,9 +78,7 @@ export class CreateCommunity extends Component<any, CreateCommunityState> { /> {this.state.loading ? ( <h5> - <svg class="icon icon-spinner spin"> - <use xlinkHref="#icon-spinner"></use> - </svg> + <Spinner /> </h5> ) : ( <div class="row"> diff --git a/src/shared/components/create-post.tsx b/src/shared/components/create-post.tsx index 2c0d9c1..8b8d2a7 100644 --- a/src/shared/components/create-post.tsx +++ b/src/shared/components/create-post.tsx @@ -2,6 +2,7 @@ import { Component } from 'inferno'; import { Subscription } from 'rxjs'; import { PostForm } from './post-form'; import { HtmlTags } from './html-tags'; +import { Spinner } from './icon'; import { authField, isBrowser, @@ -95,9 +96,7 @@ export class CreatePost extends Component<any, CreatePostState> { /> {this.state.loading ? ( <h5> - <svg class="icon icon-spinner spin"> - <use xlinkHref="#icon-spinner"></use> - </svg> + <Spinner /> </h5> ) : ( <div class="row"> diff --git a/src/shared/components/create-private-message.tsx b/src/shared/components/create-private-message.tsx index 506e3ee..b6a700d 100644 --- a/src/shared/components/create-private-message.tsx +++ b/src/shared/components/create-private-message.tsx @@ -2,6 +2,7 @@ import { Component } from 'inferno'; import { Subscription } from 'rxjs'; import { PrivateMessageForm } from './private-message-form'; import { HtmlTags } from './html-tags'; +import { Spinner } from './icon'; import { UserService, WebSocketService } from '../services'; import { SiteView, @@ -112,9 +113,7 @@ export class CreatePrivateMessage extends Component< /> {this.state.loading ? ( <h5> - <svg class="icon icon-spinner spin"> - <use xlinkHref="#icon-spinner"></use> - </svg> + <Spinner /> </h5> ) : ( <div class="row"> diff --git a/src/shared/components/icon.tsx b/src/shared/components/icon.tsx new file mode 100644 index 0000000..add6d68 --- /dev/null +++ b/src/shared/components/icon.tsx @@ -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" />; + } +} diff --git a/src/shared/components/iframely-card.tsx b/src/shared/components/iframely-card.tsx index 11bc290..3624c95 100644 --- a/src/shared/components/iframely-card.tsx +++ b/src/shared/components/iframely-card.tsx @@ -2,6 +2,7 @@ import { Component, linkEvent } from 'inferno'; import { Post } from 'lemmy-js-client'; import { mdToHtml } from '../utils'; import { i18n } from '../i18next'; +import { Icon } from './icon'; interface FramelyCardProps { post: Post; @@ -52,9 +53,7 @@ export class IFramelyCard extends Component< rel="noopener" > {new URL(post.url).hostname} - <svg class="ml-1 icon"> - <use xlinkHref="#icon-external-link"></use> - </svg> + <Icon icon="external-link" classes="ml-1" /> </a> </span>, ]} diff --git a/src/shared/components/image-upload-form.tsx b/src/shared/components/image-upload-form.tsx index 4b8866f..cb28831 100644 --- a/src/shared/components/image-upload-form.tsx +++ b/src/shared/components/image-upload-form.tsx @@ -3,6 +3,7 @@ import { pictrsUri } from '../env'; import { UserService } from '../services'; import { toast, randomStr } from '../utils'; import { i18n } from '../i18next'; +import { Icon } from './icon'; interface ImageUploadFormProps { uploadTitle: string; @@ -53,9 +54,7 @@ export class ImageUploadForm extends Component< onClick={linkEvent(this, this.handleRemoveImage)} aria-label={i18n.t('remove')} > - <svg class="icon mini-overlay"> - <use xlinkHref="#icon-x"></use> - </svg> + <Icon icon="x" classes="mini-overlay" /> </a> </span> )} diff --git a/src/shared/components/inbox.tsx b/src/shared/components/inbox.tsx index ab0c312..f77f1e2 100644 --- a/src/shared/components/inbox.tsx +++ b/src/shared/components/inbox.tsx @@ -38,6 +38,7 @@ import { CommentNodes } from './comment-nodes'; import { PrivateMessage } from './private-message'; import { HtmlTags } from './html-tags'; import { SortSelect } from './sort-select'; +import { Icon, Spinner } from './icon'; import { i18n } from '../i18next'; import { InitialFetchRequest } from 'shared/interfaces'; @@ -137,9 +138,7 @@ export class Inbox extends Component<any, InboxState> { <div class="container"> {this.state.loading ? ( <h5> - <svg class="icon icon-spinner spin"> - <use xlinkHref="#icon-spinner"></use> - </svg> + <Spinner /> </h5> ) : ( <div class="row"> @@ -157,9 +156,7 @@ export class Inbox extends Component<any, InboxState> { title="RSS" rel="noopener" > - <svg class="icon ml-2 text-muted small"> - <use xlinkHref="#icon-rss">#</use> - </svg> + <Icon icon="rss" classes="ml-2 text-muted small" /> </a> </small> </h5> diff --git a/src/shared/components/login.tsx b/src/shared/components/login.tsx index 7006ae7..7fa1702 100644 --- a/src/shared/components/login.tsx +++ b/src/shared/components/login.tsx @@ -24,6 +24,7 @@ import { } from '../utils'; import { i18n } from '../i18next'; import { HtmlTags } from './html-tags'; +import { Icon, Spinner } from './icon'; interface State { loginForm: LoginForm; @@ -148,13 +149,7 @@ export class Login extends Component<any, State> { <div class="form-group row"> <div class="col-sm-10"> <button type="submit" class="btn btn-secondary"> - {this.state.loginLoading ? ( - <svg class="icon icon-spinner spin"> - <use xlinkHref="#icon-spinner"></use> - </svg> - ) : ( - i18n.t('login') - )} + {this.state.loginLoading ? <Spinner /> : i18n.t('login')} </button> </div> </div> @@ -204,9 +199,7 @@ export class Login extends Component<any, State> { /> {!validEmail(this.state.registerForm.email) && ( <div class="mt-2 mb-0 alert alert-light" role="alert"> - <svg class="icon icon-inline mr-2"> - <use xlinkHref="#icon-alert-triangle"></use> - </svg> + <Icon icon="alert-triangle" classes="icon-inline mr-2" /> {i18n.t('no_password_reset')} </div> )} @@ -261,9 +254,7 @@ export class Login extends Component<any, State> { class="btn btn-secondary" onClick={linkEvent(this, this.handleRegenCaptcha)} > - <svg class="icon icon-refresh-cw"> - <use xlinkHref="#icon-refresh-cw"></use> - </svg> + <Icon icon="refresh-cw" classes="icon-refresh-cw" /> </button> </label> {this.showCaptcha()} @@ -303,13 +294,7 @@ export class Login extends Component<any, State> { <div class="form-group row"> <div class="col-sm-10"> <button type="submit" class="btn btn-secondary"> - {this.state.registerLoading ? ( - <svg class="icon icon-spinner spin"> - <use xlinkHref="#icon-spinner"></use> - </svg> - ) : ( - i18n.t('sign_up') - )} + {this.state.registerLoading ? <Spinner /> : i18n.t('sign_up')} </button> </div> </div> @@ -337,9 +322,7 @@ export class Login extends Component<any, State> { type="button" disabled={this.state.captchaPlaying} > - <svg class="icon icon-play"> - <use xlinkHref="#icon-play"></use> - </svg> + <Icon icon="play" classes="icon-play" /> </button> )} </> diff --git a/src/shared/components/main.tsx b/src/shared/components/main.tsx index 2d4b7d7..a80d787 100644 --- a/src/shared/components/main.tsx +++ b/src/shared/components/main.tsx @@ -34,6 +34,7 @@ import { SiteForm } from './site-form'; import { UserListing } from './user-listing'; import { CommunityLink } from './community-link'; import { BannerIconHeader } from './banner-icon-header'; +import { Icon, Spinner } from './icon'; import { wsJsonToRes, mdToHtml, @@ -513,9 +514,7 @@ export class Main extends Component<any, MainState> { aria-label={i18n.t('edit')} data-tippy-content={i18n.t('edit')} > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-edit"></use> - </svg> + <Icon icon="edit" classes="icon-inline" /> </span> </li> </ul> @@ -539,9 +538,7 @@ export class Main extends Component<any, MainState> { <div class="main-content-wrapper"> {this.state.loading ? ( <h5> - <svg class="icon icon-spinner spin"> - <use xlinkHref="#icon-spinner"></use> - </svg> + <Spinner /> </h5> ) : ( <div> @@ -601,9 +598,7 @@ export class Main extends Component<any, MainState> { rel="noopener" title="RSS" > - <svg class="icon text-muted small"> - <use xlinkHref="#icon-rss">#</use> - </svg> + <Icon icon="rss" classes="text-muted small" /> </a> )} {this.state.listingType == ListingType.Local && ( @@ -613,9 +608,7 @@ export class Main extends Component<any, MainState> { rel="noopener" title="RSS" > - <svg class="icon text-muted small"> - <use xlinkHref="#icon-rss">#</use> - </svg> + <Icon icon="rss" classes="text-muted small" /> </a> )} {UserService.Instance.user && @@ -626,9 +619,7 @@ export class Main extends Component<any, MainState> { title="RSS" rel="noopener" > - <svg class="icon text-muted small"> - <use xlinkHref="#icon-rss">#</use> - </svg> + <Icon icon="rss" classes="text-muted small" /> </a> )} </div> diff --git a/src/shared/components/markdown-textarea.tsx b/src/shared/components/markdown-textarea.tsx index cffc432..bc53c74 100644 --- a/src/shared/components/markdown-textarea.tsx +++ b/src/shared/components/markdown-textarea.tsx @@ -14,6 +14,7 @@ import { UserService } from '../services'; import autosize from 'autosize'; import { i18n } from '../i18next'; import { pictrsUri } from '../env'; +import { Icon, Spinner } from './icon'; interface MarkdownTextAreaProps { initialContent: string; @@ -148,9 +149,7 @@ export class MarkdownTextArea extends Component< disabled={this.props.disabled || this.state.loading} > {this.state.loading ? ( - <svg class="icon icon-spinner spin"> - <use xlinkHref="#icon-spinner"></use> - </svg> + <Spinner /> ) : ( <span>{this.props.buttonTitle}</span> )} @@ -182,27 +181,21 @@ export class MarkdownTextArea extends Component< data-tippy-content={i18n.t('bold')} onClick={linkEvent(this, this.handleInsertBold)} > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-bold"></use> - </svg> + <Icon icon="bold" classes="icon-inline" /> </button> <button class="btn btn-sm text-muted" data-tippy-content={i18n.t('italic')} onClick={linkEvent(this, this.handleInsertItalic)} > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-italic"></use> - </svg> + <Icon icon="italic" classes="icon-inline" /> </button> <button class="btn btn-sm text-muted" data-tippy-content={i18n.t('link')} onClick={linkEvent(this, this.handleInsertLink)} > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-link"></use> - </svg> + <Icon icon="link" classes="icon-inline" /> </button> <form class="btn btn-sm text-muted font-weight-bold"> <label @@ -211,13 +204,9 @@ export class MarkdownTextArea extends Component< data-tippy-content={i18n.t('upload_image')} > {this.state.imageLoading ? ( - <svg class="icon icon-spinner spin"> - <use xlinkHref="#icon-spinner"></use> - </svg> + <Spinner /> ) : ( - <svg class="icon icon-inline"> - <use xlinkHref="#icon-image"></use> - </svg> + <Icon icon="image" classes="icon-inline" /> )} </label> <input @@ -236,9 +225,7 @@ export class MarkdownTextArea extends Component< aria-label={i18n.t('header')} onClick={linkEvent(this, this.handleInsertHeader)} > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-header"></use> - </svg> + <Icon icon="header" classes="icon-inline" /> </button> <button class="btn btn-sm text-muted" @@ -246,9 +233,7 @@ export class MarkdownTextArea extends Component< aria-label={i18n.t('strikethrough')} onClick={linkEvent(this, this.handleInsertStrikethrough)} > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-strikethrough"></use> - </svg> + <Icon icon="strikethrough" classes="icon-inline" /> </button> <button class="btn btn-sm text-muted" @@ -256,9 +241,7 @@ export class MarkdownTextArea extends Component< aria-label={i18n.t('quote')} onClick={linkEvent(this, this.handleInsertQuote)} > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-format_quote"></use> - </svg> + <Icon icon="format_quote" classes="icon-inline" /> </button> <button class="btn btn-sm text-muted" @@ -266,9 +249,7 @@ export class MarkdownTextArea extends Component< aria-label={i18n.t('list')} onClick={linkEvent(this, this.handleInsertList)} > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-list"></use> - </svg> + <Icon icon="list" classes="icon-inline" /> </button> <button class="btn btn-sm text-muted" @@ -276,9 +257,7 @@ export class MarkdownTextArea extends Component< aria-label={i18n.t('code')} onClick={linkEvent(this, this.handleInsertCode)} > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-code"></use> - </svg> + <Icon icon="code" classes="icon-inline" /> </button> <button class="btn btn-sm text-muted" @@ -286,9 +265,7 @@ export class MarkdownTextArea extends Component< aria-label={i18n.t('subscript')} onClick={linkEvent(this, this.handleInsertSubscript)} > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-subscript"></use> - </svg> + <Icon icon="subscript" classes="icon-inline" /> </button> <button class="btn btn-sm text-muted" @@ -296,9 +273,7 @@ export class MarkdownTextArea extends Component< aria-label={i18n.t('superscript')} onClick={linkEvent(this, this.handleInsertSuperscript)} > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-superscript"></use> - </svg> + <Icon icon="superscript" classes="icon-inline" /> </button> <button class="btn btn-sm text-muted" @@ -306,9 +281,7 @@ export class MarkdownTextArea extends Component< aria-label={i18n.t('spoiler')} onClick={linkEvent(this, this.handleInsertSpoiler)} > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-alert-triangle"></use> - </svg> + <Icon icon="alert-triangle" classes="icon-inline" /> </button> <a href={markdownHelpUrl} @@ -317,9 +290,7 @@ export class MarkdownTextArea extends Component< title={i18n.t('formatting_help')} rel="noopener" > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-help-circle"></use> - </svg> + <Icon icon="help-circle" classes="icon-inline" /> </a> </div> </div> diff --git a/src/shared/components/modlog.tsx b/src/shared/components/modlog.tsx index cfdaef7..f92fa83 100644 --- a/src/shared/components/modlog.tsx +++ b/src/shared/components/modlog.tsx @@ -34,6 +34,7 @@ import { i18n } from '../i18next'; import { InitialFetchRequest } from 'shared/interfaces'; import { UserListing } from './user-listing'; import { CommunityLink } from './community-link'; +import { Spinner } from './icon'; enum ModlogEnum { ModRemovePost, @@ -364,10 +365,8 @@ export class Modlog extends Component<any, ModlogState> { path={this.context.router.route.match.url} /> {this.state.loading ? ( - <h5 class=""> - <svg class="icon icon-spinner spin"> - <use xlinkHref="#icon-spinner"></use> - </svg> + <h5> + <Spinner /> </h5> ) : ( <div> diff --git a/src/shared/components/moment-time.tsx b/src/shared/components/moment-time.tsx index baa3c25..3efabdf 100644 --- a/src/shared/components/moment-time.tsx +++ b/src/shared/components/moment-time.tsx @@ -2,6 +2,7 @@ import { Component } from 'inferno'; import moment from 'moment'; import { getMomentLanguage, capitalizeFirstLetter } from '../utils'; import { i18n } from '../i18next'; +import { Icon } from './icon'; interface MomentTimeProps { data: { @@ -31,9 +32,7 @@ export class MomentTime extends Component<MomentTimeProps, any> { )} ${this.format(this.props.data.updated)}`} className="font-italics pointer unselectable" > - <svg class="icon icon-inline mr-1"> - <use xlinkHref="#icon-edit-2"></use> - </svg> + <Icon icon="edit-2" classes="icon-inline mr-1" /> {moment.utc(this.props.data.updated).fromNow(!this.props.showAgo)} </span> ); diff --git a/src/shared/components/navbar.tsx b/src/shared/components/navbar.tsx index b4d2cbe..a0ba817 100644 --- a/src/shared/components/navbar.tsx +++ b/src/shared/components/navbar.tsx @@ -35,6 +35,7 @@ import { } from '../utils'; import { i18n } from '../i18next'; import { PictrsImage } from './pictrs-image'; +import { Icon } from './icon'; interface NavbarProps { site_res: GetSiteResponse; @@ -198,9 +199,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> { to="/inbox" title={i18n.t('inbox')} > - <svg class="icon"> - <use xlinkHref="#icon-bell"></use> - </svg> + <Icon icon="bell" /> {this.state.unreadCount > 0 && ( <span class="mx-1 badge badge-light"> {this.state.unreadCount} @@ -215,9 +214,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> { onClick={linkEvent(this, this.expandNavbar)} data-tippy-content={i18n.t('expand_here')} > - <svg class="icon"> - <use xlinkHref="#icon-menu"></use> - </svg> + <Icon icon="menu" /> </button> <div className={`${!this.state.expanded && 'collapse'} navbar-collapse`} @@ -259,9 +256,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> { title={i18n.t('support_lemmy')} href={supportLemmyUrl} > - <svg class="icon small"> - <use xlinkHref="#icon-beer"></use> - </svg> + <Icon icon="beer" classes="small" /> </a> </li> </ul> @@ -273,9 +268,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> { to={`/admin`} title={i18n.t('admin_settings')} > - <svg class="icon"> - <use xlinkHref="#icon-settings"></use> - </svg> + <Icon icon="settings" /> </Link> </li> )} @@ -304,9 +297,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> { class="px-1 btn btn-link" style="color: var(--gray)" > - <svg class="icon"> - <use xlinkHref="#icon-search"></use> - </svg> + <Icon icon="search" /> </button> </form> )} @@ -319,9 +310,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> { to="/inbox" title={i18n.t('inbox')} > - <svg class="icon"> - <use xlinkHref="#icon-bell"></use> - </svg> + <Icon icon="bell" /> {this.state.unreadCount > 0 && ( <span class="ml-1 badge badge-light"> {this.state.unreadCount} diff --git a/src/shared/components/password_change.tsx b/src/shared/components/password_change.tsx index 373d10f..db9ddd8 100644 --- a/src/shared/components/password_change.tsx +++ b/src/shared/components/password_change.tsx @@ -19,6 +19,7 @@ import { } from '../utils'; import { i18n } from '../i18next'; import { HtmlTags } from './html-tags'; +import { Spinner } from './icon'; interface State { passwordChangeForm: PasswordChangeForm; @@ -113,9 +114,7 @@ export class PasswordChange extends Component<any, State> { <div class="col-sm-10"> <button type="submit" class="btn btn-secondary"> {this.state.loading ? ( - <svg class="icon icon-spinner spin"> - <use xlinkHref="#icon-spinner"></use> - </svg> + <Spinner /> ) : ( capitalizeFirstLetter(i18n.t('save')) )} diff --git a/src/shared/components/post-form.tsx b/src/shared/components/post-form.tsx index 553a6e6..86e6603 100644 --- a/src/shared/components/post-form.tsx +++ b/src/shared/components/post-form.tsx @@ -2,6 +2,7 @@ import { Component, linkEvent } from 'inferno'; import { Prompt } from 'inferno-router'; import { PostListings } from './post-listings'; import { MarkdownTextArea } from './markdown-textarea'; +import { Icon, Spinner } from './icon'; import { Subscription } from 'rxjs'; import { CreatePost, @@ -196,9 +197,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> { } d-inline-block float-right text-muted font-weight-bold`} data-tippy-content={i18n.t('upload_image')} > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-image"></use> - </svg> + <Icon icon="image" classes="icon-inline" /> </label> <input id="file-upload" @@ -222,11 +221,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> { {i18n.t('archive_link')} </a> )} - {this.state.imageLoading && ( - <svg class="icon icon-spinner spin"> - <use xlinkHref="#icon-spinner"></use> - </svg> - )} + {this.state.imageLoading && <Spinner />} {isImage(this.state.postForm.url) && ( <img src={this.state.postForm.url} class="img-fluid" alt="" /> )} @@ -347,9 +342,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> { class="btn btn-secondary mr-2" > {this.state.loading ? ( - <svg class="icon icon-spinner spin"> - <use xlinkHref="#icon-spinner"></use> - </svg> + <Spinner /> ) : this.props.post_view ? ( capitalizeFirstLetter(i18n.t('save')) ) : ( diff --git a/src/shared/components/post-listing.tsx b/src/shared/components/post-listing.tsx index 2d0192b..d68805b 100644 --- a/src/shared/components/post-listing.tsx +++ b/src/shared/components/post-listing.tsx @@ -25,6 +25,7 @@ import { IFramelyCard } from './iframely-card'; import { UserListing } from './user-listing'; import { CommunityLink } from './community-link'; import { PictrsImage } from './pictrs-image'; +import { Icon } from './icon'; import { md, mdToHtml, @@ -205,9 +206,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { aria-label={i18n.t('expand_here')} > {this.imgThumb(this.getImageSrc())} - <svg class="icon mini-overlay"> - <use xlinkHref="#icon-image"></use> - </svg> + <Icon icon="image" classes="mini-overlay" /> </div> ); } else if (post.thumbnail_url) { @@ -220,9 +219,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { title={post.url} > {this.imgThumb(this.getImageSrc())} - <svg class="icon mini-overlay"> - <use xlinkHref="#icon-external-link"></use> - </svg> + <Icon icon="external-link" classes="mini-overlay" /> </a> ); } else if (post.url) { @@ -250,9 +247,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { rel="noopener" > <div class="thumbnail rounded bg-light d-flex justify-content-center"> - <svg class="icon d-flex align-items-center"> - <use xlinkHref="#icon-external-link"></use> - </svg> + <Icon icon="external-link" classes="d-flex align-items-center" /> </div> </a> ); @@ -265,9 +260,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { title={i18n.t('comments')} > <div class="thumbnail rounded bg-light d-flex justify-content-center"> - <svg class="icon d-flex align-items-center"> - <use xlinkHref="#icon-message-square"></use> - </svg> + <Icon icon="message-square" classes="d-flex align-items-center" /> </div> </Link> ); @@ -334,9 +327,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { aria-label={i18n.t('upvote')} to={`/post/${post_view.post.id}`} > - <svg class="mr-1 icon icon-inline"> - <use xlinkHref="#icon-book-open"></use> - </svg> + <Icon icon="book-open" classes="icon-inline mr-1" /> </Link> </li> </> @@ -356,9 +347,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { data-tippy-content={i18n.t('upvote')} aria-label={i18n.t('upvote')} > - <svg class="icon upvote"> - <use xlinkHref="#icon-arrow-up1"></use> - </svg> + <Icon icon="arrow-up1" classes="upvote" /> </button> <div 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')} aria-label={i18n.t('downvote')} > - <svg class="icon downvote"> - <use xlinkHref="#icon-arrow-down1"></use> - </svg> + <Icon icon="arrow-down1" classes="downvote" /> </button> )} </div> @@ -415,9 +402,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { data-tippy-content={i18n.t('expand_here')} onClick={linkEvent(this, this.handleImageExpandClick)} > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-plus-square"></use> - </svg> + <Icon icon="plus-square" classes="icon-inline" /> </span> ) : ( <span> @@ -425,9 +410,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { class="text-monospace unselectable pointer ml-2 text-muted small" onClick={linkEvent(this, this.handleImageExpandClick)} > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-minus-square"></use> - </svg> + <Icon icon="minus-square" classes="icon-inline" /> </span> <div> <span @@ -449,9 +432,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { className="unselectable pointer ml-2 text-muted font-italic" data-tippy-content={i18n.t('deleted')} > - <svg class={`icon icon-inline text-danger`}> - <use xlinkHref="#icon-trash"></use> - </svg> + <Icon icon="trash" classes="icon-inline text-danger" /> </small> )} {post.locked && ( @@ -459,9 +440,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { className="unselectable pointer ml-2 text-muted font-italic" data-tippy-content={i18n.t('locked')} > - <svg class={`icon icon-inline text-danger`}> - <use xlinkHref="#icon-lock"></use> - </svg> + <Icon icon="lock" classes="icon-inline text-danger" /> </small> )} {post.stickied && ( @@ -469,9 +448,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { className="unselectable pointer ml-2 text-muted font-italic" data-tippy-content={i18n.t('stickied')} > - <svg class={`icon icon-inline text-primary`}> - <use xlinkHref="#icon-pin"></use> - </svg> + <Icon icon="pin" classes="icon-inline text-primary" /> </small> )} {post.nsfw && ( @@ -496,9 +473,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { })} to={`/post/${post_view.post.id}`} > - <svg class="mr-1 icon icon-inline"> - <use xlinkHref="#icon-message-square"></use> - </svg> + <Icon icon="message-square" classes="icon-inline mr-1" /> {i18n.t('number_of_comments', { count: post_view.counts.comments, })} @@ -513,9 +488,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { aria-label={i18n.t('downvote')} > <small> - <svg class="icon icon-inline mr-1"> - <use xlinkHref="#icon-arrow-down1"></use> - </svg> + <Icon icon="arrow-down1" classes="icon-inline mr-1" /> <span>{this.state.downvotes}</span> </small> </button> @@ -530,13 +503,10 @@ export class PostListing extends Component<PostListingProps, PostListingState> { aria-label={post_view.saved ? i18n.t('unsave') : i18n.t('save')} > <small> - <svg - class={`icon icon-inline ${ - post_view.saved && 'text-warning' - }`} - > - <use xlinkHref="#icon-star"></use> - </svg> + <Icon + icon="star" + classes={`icon-inline ${post_view.saved && 'text-warning'}`} + /> </small> </button> )} @@ -555,9 +525,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { onClick={linkEvent(this, this.handlePostLike)} aria-label={i18n.t('upvote')} > - <svg class="small icon icon-inline mr-2"> - <use xlinkHref="#icon-arrow-up1"></use> - </svg> + <Icon icon="arrow-up1" classes="icon-inline small mr-2" /> {this.state.upvotes} </button> {this.props.enableDownvotes && ( @@ -569,9 +537,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { data-tippy-content={this.pointsTippy} aria-label={i18n.t('downvote')} > - <svg class="small icon icon-inline mr-2"> - <use xlinkHref="#icon-arrow-down1"></use> - </svg> + <Icon icon="arrow-down1" classes="icon-inline small mr-2" /> {this.state.downvotes !== 0 && ( <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') } > - <svg - class={`icon icon-inline ${post_view.saved && 'text-warning'}`} - > - <use xlinkHref="#icon-star"></use> - </svg> + <Icon + icon="star" + classes={`icon-inline ${post_view.saved && 'text-warning'}`} + /> </button> {!this.state.showMoreMobile && this.props.showBody && ( @@ -600,9 +565,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { aria-label={i18n.t('more')} data-tippy-content={i18n.t('more')} > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-more-vertical"></use> - </svg> + <Icon icon="more-vertical" classes="icon-inline" /> </button> )} {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') } > - <svg - class={`icon icon-inline ${ - post_view.saved && 'text-warning' - }`} - > - <use xlinkHref="#icon-star"></use> - </svg> + <Icon + icon="star" + classes={`icon-inline ${post_view.saved && 'text-warning'}`} + /> </button> )} <Link @@ -669,9 +629,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { to={`/create_post${this.crossPostParams}`} title={i18n.t('cross_post')} > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-copy"></use> - </svg> + <Icon icon="copy" classes="icon-inline" /> </Link> </> )} @@ -682,9 +640,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { onClick={linkEvent(this, this.handleEditClick)} data-tippy-content={i18n.t('edit')} > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-edit"></use> - </svg> + <Icon icon="edit" classes="icon-inline" /> </button> <button class="btn btn-link btn-animate text-muted py-0" @@ -693,13 +649,12 @@ export class PostListing extends Component<PostListingProps, PostListingState> { !post_view.post.deleted ? i18n.t('delete') : i18n.t('restore') } > - <svg - class={`icon icon-inline ${ + <Icon + icon="trash" + classes={`icon-inline ${ post_view.post.deleted && 'text-danger' }`} - > - <use xlinkHref="#icon-trash"></use> - </svg> + /> </button> </> )} @@ -711,9 +666,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { data-tippy-content={i18n.t('more')} aria-label={i18n.t('more')} > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-more-vertical"></use> - </svg> + <Icon icon="more-vertical" classes="icon-inline" /> </button> ) : ( <> @@ -723,13 +676,12 @@ export class PostListing extends Component<PostListingProps, PostListingState> { onClick={linkEvent(this, this.handleViewSource)} data-tippy-content={i18n.t('view_source')} > - <svg - class={`icon icon-inline ${ + <Icon + icon="file-text" + classes={`icon-inline ${ this.state.viewSource && 'text-success' }`} - > - <use xlinkHref="#icon-file-text"></use> - </svg> + /> </button> )} {this.canModOnSelf && ( @@ -741,13 +693,12 @@ export class PostListing extends Component<PostListingProps, PostListingState> { post_view.post.locked ? i18n.t('unlock') : i18n.t('lock') } > - <svg - class={`icon icon-inline ${ + <Icon + icon="lock" + classes={`icon-inline ${ post_view.post.locked && 'text-danger' }`} - > - <use xlinkHref="#icon-lock"></use> - </svg> + /> </button> <button class="btn btn-link btn-animate text-muted py-0" @@ -758,13 +709,12 @@ export class PostListing extends Component<PostListingProps, PostListingState> { : i18n.t('sticky') } > - <svg - class={`icon icon-inline ${ + <Icon + icon="pin" + classes={`icon-inline ${ post_view.post.stickied && 'text-success' }`} - > - <use xlinkHref="#icon-pin"></use> - </svg> + /> </button> </> )} diff --git a/src/shared/components/post.tsx b/src/shared/components/post.tsx index 6db6104..519c5fa 100644 --- a/src/shared/components/post.tsx +++ b/src/shared/components/post.tsx @@ -1,5 +1,6 @@ import { Component, linkEvent } from 'inferno'; import { HtmlTags } from './html-tags'; +import { Spinner } from './icon'; import { Subscription } from 'rxjs'; import { UserOperation, @@ -256,9 +257,7 @@ export class Post extends Component<any, PostState> { <div class="container"> {this.state.loading ? ( <h5> - <svg class="icon icon-spinner spin"> - <use xlinkHref="#icon-spinner"></use> - </svg> + <Spinner /> </h5> ) : ( <div class="row"> diff --git a/src/shared/components/private-message-form.tsx b/src/shared/components/private-message-form.tsx index b4a43a1..7a1507d 100644 --- a/src/shared/components/private-message-form.tsx +++ b/src/shared/components/private-message-form.tsx @@ -23,6 +23,7 @@ import { } from '../utils'; import { UserListing } from './user-listing'; import { MarkdownTextArea } from './markdown-textarea'; +import { Icon, Spinner } from './icon'; import { i18n } from '../i18next'; import { T } from 'inferno-i18next'; @@ -121,9 +122,7 @@ export class PrivateMessageForm extends Component< data-tippy-content={i18n.t('disclaimer')} aria-label={i18n.t('disclaimer')} > - <svg class={`icon icon-inline`}> - <use xlinkHref="#icon-alert-triangle"></use> - </svg> + <Icon icon="alert-triangle" classes="icon-inline" /> </span> </label> <div class="col-sm-10"> @@ -161,9 +160,7 @@ export class PrivateMessageForm extends Component< disabled={this.state.loading} > {this.state.loading ? ( - <svg class="icon icon-spinner spin"> - <use xlinkHref="#icon-spinner"></use> - </svg> + <Spinner /> ) : this.props.privateMessage ? ( capitalizeFirstLetter(i18n.t('save')) ) : ( diff --git a/src/shared/components/private-message.tsx b/src/shared/components/private-message.tsx index 7b22b74..9aeb665 100644 --- a/src/shared/components/private-message.tsx +++ b/src/shared/components/private-message.tsx @@ -10,6 +10,7 @@ import { authField, mdToHtml, toast, wsClient } from '../utils'; import { MomentTime } from './moment-time'; import { PrivateMessageForm } from './private-message-form'; import { UserListing } from './user-listing'; +import { Icon } from './icon'; import { i18n } from '../i18next'; interface PrivateMessageState { @@ -82,13 +83,9 @@ export class PrivateMessage extends Component< onClick={linkEvent(this, this.handleMessageCollapse)} > {this.state.collapsed ? ( - <svg class="icon icon-inline"> - <use xlinkHref="#icon-plus-square"></use> - </svg> + <Icon icon="plus-square" classes="icon-inline" /> ) : ( - <svg class="icon icon-inline"> - <use xlinkHref="#icon-minus-square"></use> - </svg> + <Icon icon="minus-square" classes="icon-inline" /> )} </div> </li> @@ -130,13 +127,12 @@ export class PrivateMessage extends Component< : i18n.t('mark_as_read') } > - <svg - class={`icon icon-inline ${ + <Icon + icon="check" + classes={`icon-inline ${ message_view.private_message.read && 'text-success' }`} - > - <use xlinkHref="#icon-check"></use> - </svg> + /> </button> </li> <li className="list-inline-item"> @@ -146,9 +142,7 @@ export class PrivateMessage extends Component< data-tippy-content={i18n.t('reply')} aria-label={i18n.t('reply')} > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-reply1"></use> - </svg> + <Icon icon="reply1" classes="icon-inline" /> </button> </li> </> @@ -162,9 +156,7 @@ export class PrivateMessage extends Component< data-tippy-content={i18n.t('edit')} aria-label={i18n.t('edit')} > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-edit"></use> - </svg> + <Icon icon="edit" classes="icon-inline" /> </button> </li> <li className="list-inline-item"> @@ -182,14 +174,13 @@ export class PrivateMessage extends Component< : i18n.t('restore') } > - <svg - class={`icon icon-inline ${ + <Icon + icon="trash" + classes={`icon-inline ${ message_view.private_message.deleted && 'text-danger' }`} - > - <use xlinkHref="#icon-trash"></use> - </svg> + /> </button> </li> </> @@ -201,13 +192,12 @@ export class PrivateMessage extends Component< data-tippy-content={i18n.t('view_source')} aria-label={i18n.t('view_source')} > - <svg - class={`icon icon-inline ${ + <Icon + icon="file-text" + classes={`icon-inline ${ this.state.viewSource && 'text-success' }`} - > - <use xlinkHref="#icon-file-text"></use> - </svg> + /> </button> </li> </ul> diff --git a/src/shared/components/search.tsx b/src/shared/components/search.tsx index 1b61e3b..11d9f4a 100644 --- a/src/shared/components/search.tsx +++ b/src/shared/components/search.tsx @@ -35,6 +35,7 @@ import { } from '../utils'; import { PostListing } from './post-listing'; import { HtmlTags } from './html-tags'; +import { Spinner } from './icon'; import { UserListing } from './user-listing'; import { CommunityLink } from './community-link'; import { SortSelect } from './sort-select'; @@ -215,13 +216,7 @@ export class Search extends Component<any, SearchState> { minLength={3} /> <button type="submit" class="btn btn-secondary mr-2 mb-2"> - {this.state.loading ? ( - <svg class="icon icon-spinner spin"> - <use xlinkHref="#icon-spinner"></use> - </svg> - ) : ( - <span>{i18n.t('search')}</span> - )} + {this.state.loading ? <Spinner /> : <span>{i18n.t('search')}</span>} </button> </form> ); diff --git a/src/shared/components/setup.tsx b/src/shared/components/setup.tsx index 1abd1c4..c833a0f 100644 --- a/src/shared/components/setup.tsx +++ b/src/shared/components/setup.tsx @@ -6,6 +6,7 @@ import { Register, LoginResponse, UserOperation } from 'lemmy-js-client'; import { WebSocketService, UserService } from '../services'; import { wsUserOp, wsJsonToRes, toast, wsClient } from '../utils'; import { SiteForm } from './site-form'; +import { Spinner } from './icon'; import { i18n } from '../i18next'; interface State { @@ -143,13 +144,7 @@ export class Setup extends Component<any, State> { <div class="form-group row"> <div class="col-sm-10"> <button type="submit" class="btn btn-secondary"> - {this.state.userLoading ? ( - <svg class="icon icon-spinner spin"> - <use xlinkHref="#icon-spinner"></use> - </svg> - ) : ( - i18n.t('sign_up') - )} + {this.state.userLoading ? <Spinner /> : i18n.t('sign_up')} </button> </div> </div> diff --git a/src/shared/components/sidebar.tsx b/src/shared/components/sidebar.tsx index db96efa..02525b8 100644 --- a/src/shared/components/sidebar.tsx +++ b/src/shared/components/sidebar.tsx @@ -16,6 +16,7 @@ import { CommunityForm } from './community-form'; import { UserListing } from './user-listing'; import { CommunityLink } from './community-link'; import { BannerIconHeader } from './banner-icon-header'; +import { Icon } from './icon'; import { i18n } from '../i18next'; interface SidebarProps { @@ -108,9 +109,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> { href="#" onClick={linkEvent(community.id, this.handleUnsubscribe)} > - <svg class="text-success mr-1 icon icon-inline"> - <use xlinkHref="#icon-check"></use> - </svg> + <Icon icon="check" classes="icon-inline text-success mr-1" /> {i18n.t('joined')} </a> )} @@ -305,9 +304,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> { data-tippy-content={i18n.t('edit')} aria-label={i18n.t('edit')} > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-edit"></use> - </svg> + <Icon icon="edit" classes="icon-inline" /> </span> </li> {!this.amCreator && @@ -368,13 +365,12 @@ export class Sidebar extends Component<SidebarProps, SidebarState> { : i18n.t('restore') } > - <svg - class={`icon icon-inline ${ + <Icon + icon="trash" + classes={`icon-inline ${ community_view.community.deleted && 'text-danger' }`} - > - <use xlinkHref="#icon-trash"></use> - </svg> + /> </span> </li> )} diff --git a/src/shared/components/site-form.tsx b/src/shared/components/site-form.tsx index 2446b05..6a4211b 100644 --- a/src/shared/components/site-form.tsx +++ b/src/shared/components/site-form.tsx @@ -1,6 +1,7 @@ import { Component, linkEvent } from 'inferno'; import { Prompt } from 'inferno-router'; import { MarkdownTextArea } from './markdown-textarea'; +import { Spinner } from './icon'; import { ImageUploadForm } from './image-upload-form'; import { Site, EditSite } from 'lemmy-js-client'; import { WebSocketService } from '../services'; @@ -220,9 +221,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { disabled={this.state.loading} > {this.state.loading ? ( - <svg class="icon icon-spinner spin"> - <use xlinkHref="#icon-spinner"></use> - </svg> + <Spinner /> ) : this.props.site ? ( capitalizeFirstLetter(i18n.t('save')) ) : ( diff --git a/src/shared/components/sort-select.tsx b/src/shared/components/sort-select.tsx index 5ed2bc6..ca6984f 100644 --- a/src/shared/components/sort-select.tsx +++ b/src/shared/components/sort-select.tsx @@ -1,6 +1,7 @@ import { Component, linkEvent } from 'inferno'; import { SortType } from 'lemmy-js-client'; import { sortingHelpUrl, randomStr } from '../utils'; +import { Icon } from './icon'; import { i18n } from '../i18next'; interface SortSelectProps { @@ -42,7 +43,9 @@ export class SortSelect extends Component<SortSelectProps, SortSelectState> { class="custom-select w-auto mr-2 mb-2" 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 && [ <option value={SortType.Hot}>{i18n.t('hot')}</option>, <option value={SortType.Active}>{i18n.t('active')}</option>, @@ -53,7 +56,9 @@ export class SortSelect extends Component<SortSelectProps, SortSelectState> { {i18n.t('most_comments')} </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.TopWeek}>{i18n.t('top_week')}</option> <option value={SortType.TopMonth}>{i18n.t('top_month')}</option> @@ -67,9 +72,7 @@ export class SortSelect extends Component<SortSelectProps, SortSelectState> { rel="noopener" title={i18n.t('sorting_help')} > - <svg class={`icon icon-inline`}> - <use xlinkHref="#icon-help-circle"></use> - </svg> + <Icon icon="help-circle" classes="icon-inline" /> </a> </> ); diff --git a/src/shared/components/user.tsx b/src/shared/components/user.tsx index 0dbe252..09858bc 100644 --- a/src/shared/components/user.tsx +++ b/src/shared/components/user.tsx @@ -57,6 +57,7 @@ import { i18n } from '../i18next'; import moment from 'moment'; import { UserDetails } from './user-details'; import { MarkdownTextArea } from './markdown-textarea'; +import { Icon, Spinner } from './icon'; import { ImageUploadForm } from './image-upload-form'; import { BannerIconHeader } from './banner-icon-header'; import { CommunityLink } from './community-link'; @@ -272,9 +273,7 @@ export class User extends Component<any, UserState> { <div class="container"> {this.state.loading ? ( <h5> - <svg class="icon icon-spinner spin"> - <use xlinkHref="#icon-spinner"></use> - </svg> + <Spinner /> </h5> ) : ( <div class="row"> @@ -393,9 +392,7 @@ export class User extends Component<any, UserState> { rel="noopener" title="RSS" > - <svg class="icon mx-2 text-muted small"> - <use xlinkHref="#icon-rss">#</use> - </svg> + <Icon icon="rss" classes="text-muted small mx-2" /> </a> </div> ); @@ -485,9 +482,7 @@ export class User extends Component<any, UserState> { <MomentTime data={uv.user} showAgo ignoreUpdated /> </div> <div className="d-flex align-items-center text-muted mb-2"> - <svg class="icon"> - <use xlinkHref="#icon-cake"></use> - </svg> + <Icon icon="cake" /> <span className="ml-2"> {i18n.t('cake_day_title')}{' '} {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"> <button type="submit" class="btn btn-block btn-secondary mr-4"> {this.state.userSettingsLoading ? ( - <svg class="icon icon-spinner spin"> - <use xlinkHref="#icon-spinner"></use> - </svg> + <Spinner /> ) : ( capitalizeFirstLetter(i18n.t('save')) )} @@ -827,9 +820,7 @@ export class User extends Component<any, UserState> { onClick={linkEvent(this, this.handleDeleteAccount)} > {this.state.deleteAccountLoading ? ( - <svg class="icon icon-spinner spin"> - <use xlinkHref="#icon-spinner"></use> - </svg> + <Spinner /> ) : ( capitalizeFirstLetter(i18n.t('delete')) )}