Add user bios (#1043)
* Add user bios * Version v0.7.35 * Add domain name change instructions to docs. (#1044) * Add domain name change instructions to docs. * Changing docker execs to docker-compose execs * Set maxLength to user bio and render as md * Fix bio updating after SaveUserSetting Co-authored-by: Dessalines <tyhou13@gmx.com> Co-authored-by: Dessalines <dessalines@users.noreply.github.com>
This commit is contained in:
parent
e31f74c3ad
commit
1acb51105a
6 changed files with 57 additions and 6 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -16,5 +16,6 @@ ui/src/translations
|
||||||
|
|
||||||
# ide config
|
# ide config
|
||||||
.idea/
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
|
||||||
target
|
target
|
||||||
|
|
|
@ -97,6 +97,7 @@ pub struct SaveUserSettings {
|
||||||
lang: String,
|
lang: String,
|
||||||
avatar: Option<String>,
|
avatar: Option<String>,
|
||||||
email: Option<String>,
|
email: Option<String>,
|
||||||
|
bio: Option<String>,
|
||||||
matrix_user_id: Option<String>,
|
matrix_user_id: Option<String>,
|
||||||
new_password: Option<String>,
|
new_password: Option<String>,
|
||||||
new_password_verify: Option<String>,
|
new_password_verify: Option<String>,
|
||||||
|
@ -557,6 +558,17 @@ impl Perform for Oper<SaveUserSettings> {
|
||||||
None => read_user.email,
|
None => read_user.email,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let bio = match &data.bio {
|
||||||
|
Some(bio) => {
|
||||||
|
if bio.chars().count() <= 300 {
|
||||||
|
Some(bio.to_owned())
|
||||||
|
} else {
|
||||||
|
return Err(APIError::err("bio_length_overflow").into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => read_user.bio,
|
||||||
|
};
|
||||||
|
|
||||||
let avatar = match &data.avatar {
|
let avatar = match &data.avatar {
|
||||||
Some(avatar) => Some(avatar.to_owned()),
|
Some(avatar) => Some(avatar.to_owned()),
|
||||||
None => read_user.avatar,
|
None => read_user.avatar,
|
||||||
|
@ -613,7 +625,7 @@ impl Perform for Oper<SaveUserSettings> {
|
||||||
show_avatars: data.show_avatars,
|
show_avatars: data.show_avatars,
|
||||||
send_notifications_to_email: data.send_notifications_to_email,
|
send_notifications_to_email: data.send_notifications_to_email,
|
||||||
actor_id: read_user.actor_id,
|
actor_id: read_user.actor_id,
|
||||||
bio: read_user.bio,
|
bio,
|
||||||
local: read_user.local,
|
local: read_user.local,
|
||||||
private_key: read_user.private_key,
|
private_key: read_user.private_key,
|
||||||
public_key: read_user.public_key,
|
public_key: read_user.public_key,
|
||||||
|
|
3
ui/src/components/markdown-textarea.tsx
vendored
3
ui/src/components/markdown-textarea.tsx
vendored
|
@ -21,6 +21,7 @@ interface MarkdownTextAreaProps {
|
||||||
replyType?: boolean;
|
replyType?: boolean;
|
||||||
focus?: boolean;
|
focus?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
maxLength?: number;
|
||||||
onSubmit?(msg: { val: string; formId: string }): any;
|
onSubmit?(msg: { val: string; formId: string }): any;
|
||||||
onContentChange?(val: string): any;
|
onContentChange?(val: string): any;
|
||||||
onReplyCancel?(): any;
|
onReplyCancel?(): any;
|
||||||
|
@ -121,7 +122,7 @@ export class MarkdownTextArea extends Component<
|
||||||
required
|
required
|
||||||
disabled={this.props.disabled}
|
disabled={this.props.disabled}
|
||||||
rows={2}
|
rows={2}
|
||||||
maxLength={10000}
|
maxLength={this.props.maxLength || 10000}
|
||||||
/>
|
/>
|
||||||
{this.state.previewMode && (
|
{this.state.previewMode && (
|
||||||
<div
|
<div
|
||||||
|
|
40
ui/src/components/user.tsx
vendored
40
ui/src/components/user.tsx
vendored
|
@ -31,6 +31,7 @@ import {
|
||||||
toast,
|
toast,
|
||||||
setupTippy,
|
setupTippy,
|
||||||
getLanguage,
|
getLanguage,
|
||||||
|
mdToHtml,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { UserListing } from './user-listing';
|
import { UserListing } from './user-listing';
|
||||||
import { SortSelect } from './sort-select';
|
import { SortSelect } from './sort-select';
|
||||||
|
@ -39,6 +40,7 @@ import { MomentTime } from './moment-time';
|
||||||
import { i18n } from '../i18next';
|
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';
|
||||||
|
|
||||||
interface UserState {
|
interface UserState {
|
||||||
user: UserView;
|
user: UserView;
|
||||||
|
@ -109,6 +111,7 @@ export class User extends Component<any, UserState> {
|
||||||
show_avatars: null,
|
show_avatars: null,
|
||||||
send_notifications_to_email: null,
|
send_notifications_to_email: null,
|
||||||
auth: null,
|
auth: null,
|
||||||
|
bio: null,
|
||||||
},
|
},
|
||||||
userSettingsLoading: null,
|
userSettingsLoading: null,
|
||||||
deleteAccountLoading: null,
|
deleteAccountLoading: null,
|
||||||
|
@ -149,7 +152,13 @@ export class User extends Component<any, UserState> {
|
||||||
this.handleUserSettingsListingTypeChange = this.handleUserSettingsListingTypeChange.bind(
|
this.handleUserSettingsListingTypeChange = this.handleUserSettingsListingTypeChange.bind(
|
||||||
this
|
this
|
||||||
);
|
);
|
||||||
|
this.handleUserSettingsListingTypeChange = this.handleUserSettingsListingTypeChange.bind(
|
||||||
|
this
|
||||||
|
);
|
||||||
this.handlePageChange = this.handlePageChange.bind(this);
|
this.handlePageChange = this.handlePageChange.bind(this);
|
||||||
|
this.handleUserSettingsBioChange = this.handleUserSettingsBioChange.bind(
|
||||||
|
this
|
||||||
|
);
|
||||||
|
|
||||||
this.state.user_id = Number(this.props.match.params.id) || null;
|
this.state.user_id = Number(this.props.match.params.id) || null;
|
||||||
this.state.username = this.props.match.params.username;
|
this.state.username = this.props.match.params.username;
|
||||||
|
@ -375,6 +384,12 @@ export class User extends Component<any, UserState> {
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
</h5>
|
</h5>
|
||||||
|
<div className="d-flex align-items-center mb-2">
|
||||||
|
<div
|
||||||
|
className="md-div"
|
||||||
|
dangerouslySetInnerHTML={mdToHtml(user.bio)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div className="d-flex align-items-center mb-2">
|
<div className="d-flex align-items-center mb-2">
|
||||||
<svg class="icon">
|
<svg class="icon">
|
||||||
<use xlinkHref="#icon-cake"></use>
|
<use xlinkHref="#icon-cake"></use>
|
||||||
|
@ -570,6 +585,18 @@ export class User extends Component<any, UserState> {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-lg-3 col-form-label" htmlFor="user-bio">
|
||||||
|
{i18n.t('bio')}
|
||||||
|
</label>
|
||||||
|
<div class="col-lg-9">
|
||||||
|
<MarkdownTextArea
|
||||||
|
initialContent={this.state.userSettingsForm.bio}
|
||||||
|
onContentChange={this.handleUserSettingsBioChange}
|
||||||
|
maxLength={300}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label class="col-lg-5 col-form-label">
|
<label class="col-lg-5 col-form-label">
|
||||||
<a
|
<a
|
||||||
|
@ -900,6 +927,11 @@ export class User extends Component<any, UserState> {
|
||||||
i.setState(i.state);
|
i.setState(i.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleUserSettingsBioChange(val: string) {
|
||||||
|
this.state.userSettingsForm.bio = val;
|
||||||
|
this.setState(this.state);
|
||||||
|
}
|
||||||
|
|
||||||
handleUserSettingsMatrixUserIdChange(i: User, event: any) {
|
handleUserSettingsMatrixUserIdChange(i: User, event: any) {
|
||||||
i.state.userSettingsForm.matrix_user_id = event.target.value;
|
i.state.userSettingsForm.matrix_user_id = event.target.value;
|
||||||
if (
|
if (
|
||||||
|
@ -1057,6 +1089,7 @@ export class User extends Component<any, UserState> {
|
||||||
this.state.userSettingsForm.lang = UserService.Instance.user.lang;
|
this.state.userSettingsForm.lang = UserService.Instance.user.lang;
|
||||||
this.state.userSettingsForm.avatar = UserService.Instance.user.avatar;
|
this.state.userSettingsForm.avatar = UserService.Instance.user.avatar;
|
||||||
this.state.userSettingsForm.email = this.state.user.email;
|
this.state.userSettingsForm.email = this.state.user.email;
|
||||||
|
this.state.userSettingsForm.bio = this.state.user.bio;
|
||||||
this.state.userSettingsForm.send_notifications_to_email = this.state.user.send_notifications_to_email;
|
this.state.userSettingsForm.send_notifications_to_email = this.state.user.send_notifications_to_email;
|
||||||
this.state.userSettingsForm.show_avatars =
|
this.state.userSettingsForm.show_avatars =
|
||||||
UserService.Instance.user.show_avatars;
|
UserService.Instance.user.show_avatars;
|
||||||
|
@ -1068,9 +1101,10 @@ export class User extends Component<any, UserState> {
|
||||||
} else if (res.op == UserOperation.SaveUserSettings) {
|
} else if (res.op == UserOperation.SaveUserSettings) {
|
||||||
const data = res.data as LoginResponse;
|
const data = res.data as LoginResponse;
|
||||||
UserService.Instance.login(data);
|
UserService.Instance.login(data);
|
||||||
this.setState({
|
this.state.user.bio = this.state.userSettingsForm.bio;
|
||||||
userSettingsLoading: false,
|
this.state.userSettingsLoading = false;
|
||||||
});
|
this.setState(this.state);
|
||||||
|
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
} else if (res.op == UserOperation.DeleteAccount) {
|
} else if (res.op == UserOperation.DeleteAccount) {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
|
1
ui/src/interfaces.ts
vendored
1
ui/src/interfaces.ts
vendored
|
@ -597,6 +597,7 @@ export interface UserSettingsForm {
|
||||||
lang: string;
|
lang: string;
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
|
bio?: string;
|
||||||
matrix_user_id?: string;
|
matrix_user_id?: string;
|
||||||
new_password?: string;
|
new_password?: string;
|
||||||
new_password_verify?: string;
|
new_password_verify?: string;
|
||||||
|
|
4
ui/translations/en.json
vendored
4
ui/translations/en.json
vendored
|
@ -228,6 +228,7 @@
|
||||||
"landing_0":
|
"landing_0":
|
||||||
"Lemmy is a <1>link aggregator</1> / reddit alternative, intended to work in the <2>fediverse</2>.<3></3>It's self-hostable, has live-updating comment threads, and is tiny (<4>~80kB</4>). Federation into the ActivityPub network is on the roadmap. <5></5>This is a <6>very early beta version</6>, and a lot of features are currently broken or missing. <7></7>Suggest new features or report bugs <8>here.</8><9></9>Made with <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>. <14></14> <15>Thank you to our contributors: </15> dessalines, Nutomic, asonix, zacanger, and iav.",
|
"Lemmy is a <1>link aggregator</1> / reddit alternative, intended to work in the <2>fediverse</2>.<3></3>It's self-hostable, has live-updating comment threads, and is tiny (<4>~80kB</4>). Federation into the ActivityPub network is on the roadmap. <5></5>This is a <6>very early beta version</6>, and a lot of features are currently broken or missing. <7></7>Suggest new features or report bugs <8>here.</8><9></9>Made with <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>. <14></14> <15>Thank you to our contributors: </15> dessalines, Nutomic, asonix, zacanger, and iav.",
|
||||||
"not_logged_in": "Not logged in.",
|
"not_logged_in": "Not logged in.",
|
||||||
|
"bio_length_overflow": "User bio cannot exceed 300 characters!",
|
||||||
"logged_in": "Logged in.",
|
"logged_in": "Logged in.",
|
||||||
"must_login": "You must <1>log in or register</1> to comment.",
|
"must_login": "You must <1>log in or register</1> to comment.",
|
||||||
"site_saved": "Site Saved.",
|
"site_saved": "Site Saved.",
|
||||||
|
@ -284,5 +285,6 @@
|
||||||
"cake_day_info": "It's {{ creator_name }}'s cake day today!",
|
"cake_day_info": "It's {{ creator_name }}'s cake day today!",
|
||||||
"invalid_post_title": "Invalid post title",
|
"invalid_post_title": "Invalid post title",
|
||||||
"invalid_url": "Invalid URL.",
|
"invalid_url": "Invalid URL.",
|
||||||
"play_captcha_audio": "Play Captcha Audio"
|
"play_captcha_audio": "Play Captcha Audio",
|
||||||
|
"bio": "Bio"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue