summaryrefslogtreecommitdiffstats
path: root/ui
diff options
context:
space:
mode:
authorDessalines <tyhou13@gmx.com>2020-07-09 20:11:05 -0400
committerDessalines <tyhou13@gmx.com>2020-07-09 20:11:05 -0400
commitd271ae67aa45cd1e185190864facb0cd5e797a23 (patch)
tree2d720b59bd2485d7f7ffc4a641ab4683445eb20f /ui
parentdb09730d5f391c37bde87f59ed2ea6f3418034e4 (diff)
parent961d65c0ee304b97cc3932d7e2a7334a823e2969 (diff)
Merge branch 'master' into remove_twemoji
Diffstat (limited to 'ui')
-rw-r--r--ui/src/components/cake-day.tsx25
-rw-r--r--ui/src/components/comment-form.tsx237
-rw-r--r--ui/src/components/comment-node.tsx2
-rw-r--r--ui/src/components/communities.tsx2
-rw-r--r--ui/src/components/community.tsx2
-rw-r--r--ui/src/components/create-community.tsx7
-rw-r--r--ui/src/components/create-post.tsx7
-rw-r--r--ui/src/components/create-private-message.tsx7
-rw-r--r--ui/src/components/inbox.tsx30
-rw-r--r--ui/src/components/main.tsx2
-rw-r--r--ui/src/components/post-listing.tsx11
-rw-r--r--ui/src/components/search.tsx30
-rw-r--r--ui/src/components/symbols.tsx3
-rw-r--r--ui/src/components/user-listing.tsx35
-rw-r--r--ui/src/components/user.tsx55
-rw-r--r--ui/src/interfaces.ts2
-rw-r--r--ui/src/utils.ts13
-rw-r--r--ui/translations/en.json5
18 files changed, 311 insertions, 164 deletions
diff --git a/ui/src/components/cake-day.tsx b/ui/src/components/cake-day.tsx
new file mode 100644
index 00000000..f28be33c
--- /dev/null
+++ b/ui/src/components/cake-day.tsx
@@ -0,0 +1,25 @@
+import { Component } from 'inferno';
+import { i18n } from '../i18next';
+
+interface CakeDayProps {
+ creatorName: string;
+}
+
+export class CakeDay extends Component<CakeDayProps, any> {
+ render() {
+ return (
+ <div
+ className={`mx-2 d-inline-block unselectable pointer`}
+ data-tippy-content={this.cakeDayTippy()}
+ >
+ <svg class="icon icon-inline">
+ <use xlinkHref="#icon-cake"></use>
+ </svg>
+ </div>
+ );
+ }
+
+ cakeDayTippy(): string {
+ return i18n.t('cake_day_info', { creator_name: this.props.creatorName });
+ }
+}
diff --git a/ui/src/components/comment-form.tsx b/ui/src/components/comment-form.tsx
index 22f871d2..04720cbb 100644
--- a/ui/src/components/comment-form.tsx
+++ b/ui/src/components/comment-form.tsx
@@ -1,4 +1,5 @@
import { Component, linkEvent } from 'inferno';
+import { Link } from 'inferno-router';
import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
import { Prompt } from 'inferno-router';
@@ -24,6 +25,7 @@ import autosize from 'autosize';
import Tribute from 'tributejs/src/Tribute.js';
import emojiShortName from 'emoji-short-name';
import { i18n } from '../i18next';
+import { T } from 'inferno-i18next';
interface CommentFormProps {
postId?: number;
@@ -97,29 +99,31 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
componentDidMount() {
let textarea: any = document.getElementById(this.id);
- autosize(textarea);
- this.tribute.attach(textarea);
- textarea.addEventListener('tribute-replaced', () => {
- this.state.commentForm.content = textarea.value;
- this.setState(this.state);
- autosize.update(textarea);
- });
+ if (textarea) {
+ autosize(textarea);
+ this.tribute.attach(textarea);
+ textarea.addEventListener('tribute-replaced', () => {
+ this.state.commentForm.content = textarea.value;
+ this.setState(this.state);
+ autosize.update(textarea);
+ });
- // Quoting of selected text
- let selectedText = window.getSelection().toString();
- if (selectedText) {
- let quotedText =
- selectedText
- .split('\n')
- .map(t => `> ${t}`)
- .join('\n') + '\n\n';
- this.state.commentForm.content = quotedText;
- this.setState(this.state);
- // Not sure why this needs a delay
- setTimeout(() => autosize.update(textarea), 10);
- }
+ // Quoting of selected text
+ let selectedText = window.getSelection().toString();
+ if (selectedText) {
+ let quotedText =
+ selectedText
+ .split('\n')
+ .map(t => `> ${t}`)
+ .join('\n') + '\n\n';
+ this.state.commentForm.content = quotedText;
+ this.setState(this.state);
+ // Not sure why this needs a delay
+ setTimeout(() => autosize.update(textarea), 10);
+ }
- textarea.focus();
+ textarea.focus();
+ }
}
componentDidUpdate() {
@@ -142,106 +146,119 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
when={this.state.commentForm.content}
message={i18n.t('block_leaving')}
/>
- <form
- id={this.formId}
- onSubmit={linkEvent(this, this.handleCommentSubmit)}
- >
- <div class="form-group row">
- <div className={`col-sm-12`}>
- <textarea
- id={this.id}
- className={`form-control ${this.state.previewMode && 'd-none'}`}
- value={this.state.commentForm.content}
- onInput={linkEvent(this, this.handleCommentContentChange)}
- onPaste={linkEvent(this, this.handleImageUploadPaste)}
- required
- disabled={this.props.disabled}
- rows={2}
- maxLength={10000}
- />
- {this.state.previewMode && (
- <div
- className="card card-body md-div"
- dangerouslySetInnerHTML={mdToHtml(
- this.state.commentForm.content
- )}
+ {UserService.Instance.user ? (
+ <form
+ id={this.formId}
+ onSubmit={linkEvent(this, this.handleCommentSubmit)}
+ >
+ <div class="form-group row">
+ <div className={`col-sm-12`}>
+ <textarea
+ id={this.id}
+ className={`form-control ${
+ this.state.previewMode && 'd-none'
+ }`}
+ value={this.state.commentForm.content}
+ onInput={linkEvent(this, this.handleCommentContentChange)}
+ onPaste={linkEvent(this, this.handleImageUploadPaste)}
+ required
+ disabled={this.props.disabled}
+ rows={2}
+ maxLength={10000}
/>
- )}
- </div>
- </div>
- <div class="row">
- <div class="col-sm-12">
- <button
- type="submit"
- class="btn btn-sm btn-secondary mr-2"
- disabled={this.props.disabled || this.state.loading}
- >
- {this.state.loading ? (
- <svg class="icon icon-spinner spin">
- <use xlinkHref="#icon-spinner"></use>
- </svg>
- ) : (
- <span>{this.state.buttonTitle}</span>
+ {this.state.previewMode && (
+ <div
+ className="card card-body md-div"
+ dangerouslySetInnerHTML={mdToHtml(
+ this.state.commentForm.content
+ )}
+ />
)}
- </button>
- {this.state.commentForm.content && (
- <button
- className={`btn btn-sm mr-2 btn-secondary ${
- this.state.previewMode && 'active'
- }`}
- onClick={linkEvent(this, this.handlePreviewToggle)}
- >
- {i18n.t('preview')}
- </button>
- )}
- {this.props.node && (
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-sm-12">
<button
- type="button"
+ type="submit"
class="btn btn-sm btn-secondary mr-2"
- onClick={linkEvent(this, this.handleReplyCancel)}
+ disabled={this.props.disabled || this.state.loading}
>
- {i18n.t('cancel')}
+ {this.state.loading ? (
+ <svg class="icon icon-spinner spin">
+ <use xlinkHref="#icon-spinner"></use>
+ </svg>
+ ) : (
+ <span>{this.state.buttonTitle}</span>
+ )}
</button>
- )}
- <a
- href={markdownHelpUrl}
- target="_blank"
- class="d-inline-block float-right text-muted font-weight-bold"
- title={i18n.t('formatting_help')}
- rel="noopener"
- >
- <svg class="icon icon-inline">
- <use xlinkHref="#icon-help-circle"></use>
- </svg>
- </a>
- <form class="d-inline-block mr-3 float-right text-muted font-weight-bold">
- <label
- htmlFor={`file-upload-${this.id}`}
- className={`${UserService.Instance.user && 'pointer'}`}
- data-tippy-content={i18n.t('upload_image')}
+ {this.state.commentForm.content && (
+ <button
+ className={`btn btn-sm mr-2 btn-secondary ${
+ this.state.previewMode && 'active'
+ }`}
+ onClick={linkEvent(this, this.handlePreviewToggle)}
+ >
+ {i18n.t('preview')}
+ </button>
+ )}
+ {this.props.node && (
+ <button
+ type="button"
+ class="btn btn-sm btn-secondary mr-2"
+ onClick={linkEvent(this, this.handleReplyCancel)}
+ >
+ {i18n.t('cancel')}
+ </button>
+ )}
+ <a
+ href={markdownHelpUrl}
+ target="_blank"
+ class="d-inline-block float-right text-muted font-weight-bold"
+ title={i18n.t('formatting_help')}
+ rel="noopener"
>
<svg class="icon icon-inline">
- <use xlinkHref="#icon-image"></use>
+ <use xlinkHref="#icon-help-circle"></use>
</svg>
- </label>
- <input
- id={`file-upload-${this.id}`}
- type="file"
- accept="image/*,video/*"
- name="file"
- class="d-none"
- disabled={!UserService.Instance.user}
- onChange={linkEvent(this, this.handleImageUpload)}
- />
- </form>
- {this.state.imageLoading && (
- <svg class="icon icon-spinner spin">
- <use xlinkHref="#icon-spinner"></use>
- </svg>
- )}
+ </a>
+ <form class="d-inline-block mr-3 float-right text-muted font-weight-bold">
+ <label
+ htmlFor={`file-upload-${this.id}`}
+ className={`${UserService.Instance.user && 'pointer'}`}
+ data-tippy-content={i18n.t('upload_image')}
+ >
+ <svg class="icon icon-inline">
+ <use xlinkHref="#icon-image"></use>
+ </svg>
+ </label>
+ <input
+ id={`file-upload-${this.id}`}
+ type="file"
+ accept="image/*,video/*"
+ name="file"
+ class="d-none"
+ disabled={!UserService.Instance.user}
+ onChange={linkEvent(this, this.handleImageUpload)}
+ />
+ </form>
+ {this.state.imageLoading && (
+ <svg class="icon icon-spinner spin">
+ <use xlinkHref="#icon-spinner"></use>
+ </svg>
+ )}
+ </div>
</div>
+ </form>
+ ) : (
+ <div class="alert alert-warning" role="alert">
+ <svg class="icon icon-inline mr-2">
+ <use xlinkHref="#icon-alert-triangle"></use>
+ </svg>
+ <T i18nKey="must_login" class="d-inline">
+ #<Link to="/login">#</Link>
+ </T>
</div>
- </form>
+ )}
</div>
);
}
diff --git a/ui/src/components/comment-node.tsx b/ui/src/components/comment-node.tsx
index 49b56629..8e976e7c 100644
--- a/ui/src/components/comment-node.tsx
+++ b/ui/src/components/comment-node.tsx
@@ -158,9 +158,11 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
id: node.comment.creator_id,
local: node.comment.creator_local,
actor_id: node.comment.creator_actor_id,
+ published: node.comment.creator_published,
}}
/>
</span>
+
{this.isMod && (
<div className="badge badge-light d-none d-sm-inline mr-2">
{i18n.t('mod')}
diff --git a/ui/src/components/communities.tsx b/ui/src/components/communities.tsx
index 441f7bb1..10a3ab80 100644
--- a/ui/src/components/communities.tsx
+++ b/ui/src/components/communities.tsx
@@ -160,7 +160,7 @@ export class Communities extends Component<any, CommunitiesState> {
</button>
)}
- {this.state.communities.length == communityLimit && (
+ {this.state.communities.length > 0 && (
<button
class="btn btn-sm btn-secondary"
onClick={linkEvent(this, this.nextPage)}
diff --git a/ui/src/components/community.tsx b/ui/src/components/community.tsx
index ff50c3dc..fc999b25 100644
--- a/ui/src/components/community.tsx
+++ b/ui/src/components/community.tsx
@@ -260,7 +260,7 @@ export class Community extends Component<any, State> {
{i18n.t('prev')}
</button>
)}
- {this.state.posts.length == fetchLimit && (
+ {this.state.posts.length > 0 && (
<button
class="btn btn-sm btn-secondary"
onClick={linkEvent(this, this.nextPage)}
diff --git a/ui/src/components/create-community.tsx b/ui/src/components/create-community.tsx
index 86929894..3a5d943d 100644
--- a/ui/src/components/create-community.tsx
+++ b/ui/src/components/create-community.tsx
@@ -9,7 +9,7 @@ import {
GetSiteResponse,
} from '../interfaces';
import { toast, wsJsonToRes } from '../utils';
-import { WebSocketService } from '../services';
+import { WebSocketService, UserService } from '../services';
import { i18n } from '../i18next';
interface CreateCommunityState {
@@ -26,6 +26,11 @@ export class CreateCommunity extends Component<any, CreateCommunityState> {
this.handleCommunityCreate = this.handleCommunityCreate.bind(this);
this.state = this.emptyState;
+ if (!UserService.Instance.user) {
+ toast(i18n.t('not_logged_in'), 'danger');
+ this.context.router.history.push(`/login`);
+ }
+
this.subscription = WebSocketService.Instance.subject
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
.subscribe(
diff --git a/ui/src/components/create-post.tsx b/ui/src/components/create-post.tsx
index 348ba0cb..4554326d 100644
--- a/ui/src/components/create-post.tsx
+++ b/ui/src/components/create-post.tsx
@@ -3,7 +3,7 @@ import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
import { PostForm } from './post-form';
import { toast, wsJsonToRes } from '../utils';
-import { WebSocketService } from '../services';
+import { WebSocketService, UserService } from '../services';
import {
UserOperation,
PostFormParams,
@@ -41,6 +41,11 @@ export class CreatePost extends Component<any, CreatePostState> {
this.handlePostCreate = this.handlePostCreate.bind(this);
this.state = this.emptyState;
+ if (!UserService.Instance.user) {
+ toast(i18n.t('not_logged_in'), 'danger');
+ this.context.router.history.push(`/login`);
+ }
+
this.subscription = WebSocketService.Instance.subject
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
.subscribe(
diff --git a/ui/src/components/create-private-message.tsx b/ui/src/components/create-private-message.tsx
index 21ed04c7..c309cbe3 100644
--- a/ui/src/components/create-private-message.tsx
+++ b/ui/src/components/create-private-message.tsx
@@ -2,7 +2,7 @@ import { Component } from 'inferno';
import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
import { PrivateMessageForm } from './private-message-form';
-import { WebSocketService } from '../services';
+import { WebSocketService, UserService } from '../services';
import {
UserOperation,
WebSocketJsonResponse,
@@ -20,6 +20,11 @@ export class CreatePrivateMessage extends Component<any, any> {
this
);
+ if (!UserService.Instance.user) {
+ toast(i18n.t('not_logged_in'), 'danger');
+ this.context.router.history.push(`/login`);
+ }
+
this.subscription = WebSocketService.Instance.subject
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
.subscribe(
diff --git a/ui/src/components/inbox.tsx b/ui/src/components/inbox.tsx
index a88d45c5..2bf1fb47 100644
--- a/ui/src/components/inbox.tsx
+++ b/ui/src/components/inbox.tsx
@@ -329,12 +329,14 @@ export class Inbox extends Component<any, InboxState> {
{i18n.t('prev')}
</button>
)}
- <button
- class="btn btn-sm btn-secondary"
- onClick={linkEvent(this, this.nextPage)}
- >
- {i18n.t('next')}
- </button>
+ {this.unreadCount() > 0 && (
+ <button
+ class="btn btn-sm btn-secondary"
+ onClick={linkEvent(this, this.nextPage)}
+ >
+ {i18n.t('next')}
+ </button>
+ )}
</div>
);
}
@@ -534,15 +536,19 @@ export class Inbox extends Component<any, InboxState> {
}
sendUnreadCount() {
- let count =
+ UserService.Instance.user.unreadCount = this.unreadCount();
+ UserService.Instance.sub.next({
+ user: UserService.Instance.user,
+ });
+ }
+
+ unreadCount(): number {
+ return (
this.state.replies.filter(r => !r.read).length +
this.state.mentions.filter(r => !r.read).length +
this.state.messages.filter(
r => !r.read && r.creator_id !== UserService.Instance.user.id
- ).length;
- UserService.Instance.user.unreadCount = count;
- UserService.Instance.sub.next({
- user: UserService.Instance.user,
- });
+ ).length
+ );
}
}
diff --git a/ui/src/components/main.tsx b/ui/src/components/main.tsx
index 9e9027d6..9063a039 100644
--- a/ui/src/components/main.tsx
+++ b/ui/src/components/main.tsx
@@ -497,7 +497,7 @@ export class Main extends Component<any, MainState> {
{i18n.t('prev')}
</button>
)}
- {this.state.posts.length == fetchLimit && (
+ {this.state.posts.length > 0 && (
<button
class="btn btn-sm btn-secondary"
onClick={linkEvent(this, this.nextPage)}
diff --git a/ui/src/components/post-listing.tsx b/ui/src/components/post-listing.tsx
index ba100647..418fe7b4 100644
--- a/ui/src/components/post-listing.tsx
+++ b/ui/src/components/post-listing.tsx
@@ -33,6 +33,7 @@ import {
setupTippy,
hostname,
previewLines,
+ toast,
} from '../utils';
import { i18n } from '../i18next';
@@ -434,8 +435,10 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
id: post.creator_id,
local: post.creator_local,
actor_id: post.creator_actor_id,
+ published: post.creator_published,
}}
/>
+
{this.isMod && (
<span className="mx-1 badge badge-light">
{i18n.t('mod')}
@@ -1030,6 +1033,10 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
}
handlePostLike(i: PostListing) {
+ if (!UserService.Instance.user) {
+ this.context.router.history.push(`/login`);
+ }
+
let new_vote = i.state.my_vote == 1 ? 0 : 1;
if (i.state.my_vote == 1) {
@@ -1057,6 +1064,10 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
}
handlePostDisLike(i: PostListing) {
+ if (!UserService.Instance.user) {
+ this.context.router.history.push(`/login`);
+ }
+
let new_vote = i.state.my_vote == -1 ? 0 : -1;
if (i.state.my_vote == 1) {
diff --git a/ui/src/components/search.tsx b/ui/src/components/search.tsx
index 2588528a..dd219ba9 100644
--- a/ui/src/components/search.tsx
+++ b/ui/src/components/search.tsx
@@ -148,7 +148,7 @@ export class Search extends Component<any, SearchState> {
{this.state.type_ == SearchType.Posts && this.posts()}
{this.state.type_ == SearchType.Communities && this.communities()}
{this.state.type_ == SearchType.Users && this.users()}
- {this.noResults()}
+ {this.resultsCount() == 0 && <span>{i18n.t('no_results')}</span>}
{this.paginator()}
</div>
);
@@ -383,26 +383,26 @@ export class Search extends Component<any, SearchState> {
{i18n.t('prev')}
</button>
)}
- <button
- class="btn btn-sm btn-secondary"
- onClick={linkEvent(this, this.nextPage)}
- >
- {i18n.t('next')}
- </button>
+
+ {this.resultsCount() > 0 && (
+ <button
+ class="btn btn-sm btn-secondary"
+ onClick={linkEvent(this, this.nextPage)}
+ >
+ {i18n.t('next')}
+ </button>
+ )}
</div>
);
}
- noResults() {
+ resultsCount(): number {
let res = this.state.searchResponse;
return (
- <div>
- {res &&
- res.posts.length == 0 &&
- res.comments.length == 0 &&
- res.communities.length == 0 &&
- res.users.length == 0 && <span>{i18n.t('no_results')}</span>}
- </div>
+ res.posts.length +
+ res.comments.length +
+ res.communities.length +
+ res.users.length
);
}
diff --git a/ui/src/components/symbols.tsx b/ui/src/components/symbols.tsx
index 77d7a086..3386dbe5 100644
--- a/ui/src/components/symbols.tsx
+++ b/ui/src/components/symbols.tsx
@@ -168,6 +168,9 @@ export class Symbols extends Component<any, any> {
<symbol id="icon-spinner" viewBox="0 0 32 32">
<path d="M16 32c-4.274 0-8.292-1.664-11.314-4.686s-4.686-7.040-4.686-11.314c0-3.026 0.849-5.973 2.456-8.522 1.563-2.478 3.771-4.48 6.386-5.791l1.344 2.682c-2.126 1.065-3.922 2.693-5.192 4.708-1.305 2.069-1.994 4.462-1.994 6.922 0 7.168 5.832 13 13 13s13-5.832 13-13c0-2.459-0.69-4.853-1.994-6.922-1.271-2.015-3.066-3.643-5.192-4.708l1.344-2.682c2.615 1.31 4.824 3.313 6.386 5.791 1.607 2.549 2.456 5.495 2.456 8.522 0 4.274-1.664 8.292-4.686 11.314s-7.040 4.686-11.314 4.686z"></path>
</symbol>
+ <symbol id="icon-cake" viewBox="0 0 24 24">
+ <path d="M 23.296875 22.394531 L 22.082031 22.394531 L 22.082031 17.007812 C 22.453125 16.699219 22.664062 16.261719 22.664062 15.796875 L 22.664062 13.984375 C 22.664062 12.996094 21.785156 12.191406 20.703125 12.191406 L 19.785156 12.191406 L 19.785156 7.785156 C 19.785156 7.050781 19.1875 6.449219 18.449219 6.449219 L 18.367188 6.449219 L 18.367188 5.96875 C 19.199219 5.675781 19.796875 4.882812 19.796875 3.957031 C 19.796875 3.644531 19.703125 3.117188 18.996094 1.800781 C 18.632812 1.121094 18.273438 0.550781 18.257812 0.527344 C 18.128906 0.320312 17.90625 0.199219 17.664062 0.199219 C 17.421875 0.199219 17.199219 0.320312 17.070312 0.527344 C 17.054688 0.550781 16.695312 1.121094 16.332031 1.800781 C 15.621094 3.117188 15.53125 3.644531 15.53125 3.957031 C 15.53125 4.882812 16.128906 5.675781 16.960938 5.96875 L 16.960938 6.449219 L 16.878906 6.449219 C 16.140625 6.449219 15.542969 7.050781 15.542969 7.785156 L 15.542969 12.191406 L 14.121094 12.191406 L 14.121094 7.785156 C 14.121094 7.050781 13.523438 6.449219 12.785156 6.449219 L 12.703125 6.449219 L 12.703125 5.96875 C 13.535156 5.675781 14.132812 4.882812 14.132812 3.957031 C 14.132812 3.644531 14.039062 3.117188 13.332031 1.800781 C 12.96875 1.121094 12.609375 0.550781 12.59375 0.527344 C 12.464844 0.320312 12.242188 0.199219 12 0.199219 C 11.757812 0.199219 11.535156 0.320312 11.40625 0.527344 C 11.390625 0.550781 11.03125 1.121094 10.667969 1.800781 C 9.960938 3.117188 9.867188 3.644531 9.867188 3.957031 C 9.867188 4.882812 10.464844 5.675781 11.296875 5.96875 L 11.296875 6.449219 L 11.214844 6.449219 C 10.476562 6.449219 9.878906 7.050781 9.878906 7.785156 L 9.878906 12.191406 L 8.457031 12.191406 L 8.457031 7.785156 C 8.457031 7.050781 7.859375 6.449219 7.121094 6.449219 L 7.039062 6.449219 L 7.039062 5.96875 C 7.871094 5.675781 8.46875 4.882812 8.46875 3.957031 C 8.46875 3.644531 8.378906 3.117188 7.667969 1.800781 C 7.304688 1.121094 6.945312 0.550781 6.929688 0.527344 C 6.800781 0.320312 6.578125 0.199219 6.335938 0.199219 C 6.09375 0.199219 5.871094 0.320312 5.742188 0.527344 C 5.726562 0.550781 5.367188 1.121094 5.003906 1.800781 C 4.296875 3.117188 4.203125 3.644531 4.203125 3.957031 C 4.203125 4.882812 4.800781 5.675781 5.632812 5.96875 L 5.632812 6.449219 L 5.550781 6.449219 C 4.8125 6.449219 4.214844 7.050781 4.214844 7.785156 L 4.214844 12.191406 L 3.296875 12.191406 C 2.214844 12.191406 1.335938 12.996094 1.335938 13.984375 L 1.335938 15.796875 C 1.335938 16.261719 1.546875 16.699219 1.917969 17.007812 L 1.917969 22.394531 L 0.703125 22.394531 C 0.316406 22.394531 0 22.710938 0 23.097656 C 0 23.488281 0.316406 23.800781 0.703125 23.800781 L 23.296875 23.800781 C 23.683594 23.800781 24 23.488281 24 23.097656 C 24 22.710938 23.683594 22.394531 23.296875 22.394531 Z M 16.9375 3.957031 C 16.941406 3.730469 17.246094 3.054688 17.664062 2.289062 C 18.082031 3.054688 18.382812 3.730469 18.390625 3.957031 C 18.390625 4.355469 18.0625 4.679688 17.664062 4.679688 C 17.265625 4.679688 16.9375 4.355469 16.9375 3.957031 Z M 16.949219 7.855469 L 18.378906 7.855469 L 18.378906 12.1875 L 16.949219 12.1875 Z M 11.273438 3.957031 C 11.277344 3.730469 11.582031 3.054688 12 2.289062 C 12.417969 3.054688 12.722656 3.730469 12.726562 3.957031 C 12.726562 4.355469 12.398438 4.679688 12 4.679688 C 11.601562 4.679688 11.273438 4.355469 11.273438 3.957031 Z M 11.285156 7.855469 L 12.714844 7.855469 L 12.714844 12.1875 L 11.285156 12.1875 Z M 5.609375 3.957031 C 5.613281 3.730469 5.917969 3.054688 6.335938 2.289062 C 6.753906 3.054688 7.058594 3.730469 7.0625 3.957031 C 7.0625 4.355469 6.734375 4.679688 6.335938 4.679688 C 5.9375 4.679688 5.609375 4.355469 5.609375 3.957031 Z M 5.621094 7.855469 L 7.050781 7.855469 L 7.050781 12.1875 L 5.621094 12.1875 Z M 20.675781 22.394531 L 3.324219 22.394531 L 3.324219 17.414062 C 3.433594 17.398438 3.546875 17.378906 3.652344 17.347656 L 5.429688 16.820312 C 6.453125 16.515625 7.582031 16.515625 8.609375 16.820312 L 10.011719 17.234375 C 10.652344 17.425781 11.324219 17.519531 12 17.519531 C 12.675781 17.519531 13.347656 17.425781 13.988281 17.234375 L 15.390625 16.820312 C 16.417969 16.515625 17.546875 16.515625 18.570312 16.820312 L 20.347656 17.347656 C 20.453125 17.378906 20.5625 17.398438 20.675781 17.414062 Z M 21.257812 15.796875 C 21.257812 15.855469 21.210938 15.902344 21.171875 15.933594 C 21.082031 16 20.925781 16.050781 20.746094 15.996094 L 18.972656 15.472656 C 17.6875 15.09375 16.273438 15.09375 14.992188 15.472656 L 13.589844 15.886719 C 12.566406 16.191406 11.433594 16.191406 10.410156 15.886719 L 9.007812 15.472656 C 8.367188 15.28125 7.691406 15.1875 7.019531 15.1875 C 6.34375 15.1875 5.671875 15.28125 5.027344 15.472656 L 3.253906 15.996094 C 3.074219 16.050781 2.917969 16 2.828125 15.933594 C 2.789062 15.902344 2.742188 15.855469 2.742188 15.796875 L 2.742188 13.984375 C 2.742188 13.800781 2.96875 13.597656 3.296875 13.597656 L 20.703125 13.597656 C 21.03125 13.597656 21.257812 13.800781 21.257812 13.984375 Z M 21.257812 15.796875 " />
+ </symbol>
</defs>
</svg>
);
diff --git a/ui/src/components/user-listing.tsx b/ui/src/components/user-listing.tsx
index 0e150b94..58475d3e 100644
--- a/ui/src/components/user-listing.tsx
+++ b/ui/src/components/user-listing.tsx
@@ -1,7 +1,13 @@
import { Component } from 'inferno';
import { Link } from 'inferno-router';
import { UserView } from '../interfaces';
-import { pictrsAvatarThumbnail, showAvatars, hostname } from '../utils';
+import {
+ pictrsAvatarThumbnail,
+ showAvatars,
+ hostname,
+ isCakeDay,
+} from '../utils';
+import { CakeDay } from './cake-day';
interface UserOther {
name: string;
@@ -9,6 +15,7 @@ interface UserOther {
avatar?: string;
local?: boolean;
actor_id?: string;
+ published?: string;
}
interface UserListingProps {
@@ -35,17 +42,21 @@ export class UserListing extends Component<UserListingProps, any> {
}
return (
- <Link className="text-body font-weight-bold" to={link}>
- {user.avatar && showAvatars() && (
- <img
- height="32"
- width="32"
- src={pictrsAvatarThumbnail(user.avatar)}
- class="rounded-circle mr-2"
- />
- )}
- <span>{name_}</span>
- </Link>
+ <>
+ <Link className="text-body font-weight-bold" to={link}>
+ {user.avatar && showAvatars() && (
+ <img
+ height="32"
+ width="32"
+ src={pictrsAvatarThumbnail(user.avatar)}
+ class="rounded-circle mr-2"
+ />
+ )}
+ <span>{name_}</span>
+ </Link>
+
+ {isCakeDay(user.published) && <CakeDay creatorName={name_} />}
+ </>
);
}
}
diff --git a/ui/src/components/user.tsx b/ui/src/components/user.tsx
index 078ce89c..0e107363 100644
--- a/ui/src/components/user.tsx
+++ b/ui/src/components/user.tsx
@@ -48,6 +48,7 @@ import { ListingTypeSelect } from './listing-type-select';
import { CommentNodes } from './comment-nodes';
import { MomentTime } from './moment-time';
import { i18n } from '../i18next';
+import moment from 'moment';
enum View {
Overview,
@@ -440,6 +441,15 @@ export class User extends Component<any, UserState> {
)}
</ul>
</h5>
+ <div className="d-flex align-items-center mb-2">
+ <svg class="icon">
+ <use xlinkHref="#icon-cake"></use>
+ </svg>
+ <span className="ml-2">
+ {i18n.t('cake_day_title')}{' '}
+ {moment.utc(user.published).local().format('MMM DD, YYYY')}
+ </span>
+ </div>
<div>
{i18n.t('joined')} <MomentTime data={user} showAgo />
</div>
@@ -525,7 +535,7 @@ export class User extends Component<any, UserState> {
htmlFor="file-upload"
class="pointer ml-4 text-muted small font-weight-bold"
>
- {!this.state.userSettingsForm.avatar ? (
+ {!this.checkSettingsAvatar ? (
<span class="btn btn-sm btn-secondary">
{i18n.t('upload_avatar')}
</span>
@@ -549,6 +559,18 @@ export class User extends Component<any, UserState> {
/>
</form>
</div>
+ {this.checkSettingsAvatar && (
+ <div class="form-group">
+ <button
+ class="btn btn-secondary btn-block"
+ onClick={linkEvent(this, this.removeAvatar)}
+ >
+ {`${capitalizeFirstLetter(i18n.t('remove'))} ${i18n.t(
+ 'avatar'
+ )}`}
+ </button>
+ </div>
+ )}
<div class="form-group">
<label>{i18n.t('language')}</label>
<select
@@ -883,12 +905,14 @@ export class User extends Component<any, UserState> {
{i18n.t('prev')}
</button>
)}
- <button
- class="btn btn-sm btn-secondary"
- onClick={linkEvent(this, this.nextPage)}
- >
- {i18n.t('next')}
- </button>
+ {this.state.comments.length + this.state.posts.length > 0 && (
+ <button
+ class="btn btn-sm btn-secondary"
+ onClick={linkEvent(this, this.nextPage)}
+ >
+ {i18n.t('next')}
+ </button>