Merge branch 'profile-edit' into shigusegubu-themes3

This commit is contained in:
Henry Jameson 2025-08-05 15:24:48 +03:00
commit 3efe6d1243
11 changed files with 159 additions and 160 deletions

View file

@ -115,7 +115,6 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
position: relative; position: relative;
display: flex;
.emoji-picker-icon { .emoji-picker-icon {
position: absolute; position: absolute;

View file

@ -54,7 +54,7 @@
type="file" type="file"
class="input image-cropper-img-input" class="input image-cropper-img-input"
:accept="mimes" :accept="mimes"
/> >
</div> </div>
</template> </template>

View file

@ -1,4 +1,18 @@
.profile-tab { .profile-tab {
// overriding global for better look
div.profile-edit {
margin: 0;
h2 {
margin-left: 1rem;
}
.user-card {
padding: 1.2em;
overflow: hidden;
}
}
.bio { .bio {
margin: 0; margin: 0;
} }

View file

@ -1,12 +1,14 @@
<template> <template>
<div class="profile-tab"> <div class="profile-tab">
<UserCard <div class="setting-item profile-edit">
:user-id="user.id" <h2>{{ $t('settings.account_profile_edit') }}</h2>
:editable="true" <UserCard
:switcher="false" :user-id="user.id"
rounded="top" :editable="true"
> :switcher="false"
</UserCard> rounded="top"
/>
</div>
<div class="setting-item"> <div class="setting-item">
<h2>{{ $t('settings.account_privacy') }}</h2> <h2>{{ $t('settings.account_privacy') }}</h2>
<ul class="setting-list"> <ul class="setting-list">

View file

@ -1,4 +1,5 @@
import merge from 'lodash/merge' import merge from 'lodash/merge'
import isEqual from 'lodash/isEqual'
import unescape from 'lodash/unescape' import unescape from 'lodash/unescape'
import ColorInput from 'src/components/color_input/color_input.vue' import ColorInput from 'src/components/color_input/color_input.vue'
@ -116,6 +117,7 @@ export default {
newShowRole: user.show_role, newShowRole: user.show_role,
newFields: user.fields?.map(field => ({ name: field.name, value: field.value })), newFields: user.fields?.map(field => ({ name: field.name, value: field.value })),
editingFields: false, editingFields: false,
} }
}, },
@ -123,6 +125,21 @@ export default {
this.$store.dispatch('fetchUserRelationship', this.user.id) this.$store.dispatch('fetchUserRelationship', this.user.id)
}, },
computed: { computed: {
somethingToSave () {
if (this.newName !== this.user.name_unescaped) return true
if (this.newBio !== unescape(this.user.description)) return true
if (this.newAvatar !== null) return true
if (this.newBanner !== null) return true
if (this.newActorType !== this.user.actor_type) return true
if (this.newBirthday !== this.user.birthday) return true
if (this.newShowBirthday !== this.user.show_birthday) return true
if (this.newShowRole !== this.user.show_role) return true
if (!isEqual(
this.newFields,
this.user.fields?.map(field => ({ name: field.name, value: field.value }))
)) return true
return false
},
groupActorAvailable () { groupActorAvailable () {
return this.$store.state.instance.groupActorAvailable return this.$store.state.instance.groupActorAvailable
}, },

View file

@ -1,15 +1,22 @@
.user-card { .user-card {
position: relative; position: relative;
z-index: 1; z-index: 1;
overflow: hidden;
// editing headers // editing headers
h4 { h4 {
line-height: 2; line-height: 2;
display: flex; display: flex;
padding: 0 1.0em; }
span { h3 {
flex: 1; padding-left: 0.6em;
margin-bottom: 0;
.button-default {
font-size: 1rem;
line-height: 2;
padding: 0 0.6em;
} }
} }
@ -68,17 +75,6 @@
--_still-image-label-visibility: hidden; --_still-image-label-visibility: hidden;
} }
.panel-heading {
text-align: center;
box-shadow: none;
background: transparent;
backdrop-filter: none;
flex-direction: column;
align-items: stretch;
// create new stacking context
position: relative;
}
.personal-marks { .personal-marks {
margin: 0.6em; margin: 0.6em;
padding: 0.6em; padding: 0.6em;
@ -157,13 +153,19 @@
border-style: solid; border-style: solid;
border-color: var(--border); border-color: var(--border);
} }
.bottom-buttons {
display: flex;
gap: 0.5em;
}
} }
.user-info { .user-info {
position: relative; position: relative;
margin: 0.6em;
margin-bottom: 0;
text-align: left; text-align: left;
display: flex;
flex-direction: column;
gap: 1em;
.user-identity { .user-identity {
position: relative; position: relative;
@ -171,7 +173,6 @@
min-height: 6em; min-height: 6em;
display: flex; display: flex;
align-items: flex-end; align-items: flex-end;
margin-bottom: 1em;
container: user-card / inline-size; container: user-card / inline-size;
> * { > * {
@ -362,52 +363,6 @@
} }
} }
.highlighter {
margin: 5em;
align-items: baseline;
line-height: 22px;
flex-wrap: wrap;
.following {
flex: 1 0 auto;
margin: 0;
margin-bottom: 0.25em;
text-align: left;
}
.highlighter {
flex: 0 1 auto;
display: flex;
flex-wrap: wrap;
margin-right: -0.5em;
align-self: start;
.userHighlightCl {
padding: 2px 10px;
flex: 1 0 auto;
}
.userHighlightSel {
padding-top: 0;
padding-bottom: 0;
flex: 1 0 auto;
}
.userHighlightText {
width: 70px;
flex: 1 0 auto;
}
.userHighlightCl,
.userHighlightText,
.userHighlightSel {
vertical-align: top;
margin-right: 0.5em;
margin-bottom: 0.25em;
}
}
}
.user-interactions { .user-interactions {
position: relative; position: relative;
display: flex; display: flex;
@ -415,7 +370,7 @@
gap: 0.6em; gap: 0.6em;
> * { > * {
flex: 0 0 10%; flex: 0 0 8em;
} }
.popover-trigger-button, .moderation-tools-button { .popover-trigger-button, .moderation-tools-button {
@ -476,6 +431,7 @@
.user-profile-fields { .user-profile-fields {
margin: 0 0.5em; margin: 0 0.5em;
padding: 0 0.5em;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -484,8 +440,6 @@
img { img {
object-fit: contain; object-fit: contain;
vertical-align: middle; vertical-align: middle;
max-width: 100%;
max-height: 400px;
} }
.user-profile-field-add, .user-profile-field-add,

View file

@ -3,10 +3,7 @@
class="user-card" class="user-card"
:class="classes" :class="classes"
> >
<div <div class="user-card-inner">
:class="onClose ? '' : 'panel-heading -flexible-height'"
class="user-card-inner"
>
<div class="user-info"> <div class="user-info">
<div class="user-identity"> <div class="user-identity">
<div <div
@ -156,8 +153,8 @@
<button <button
v-if="editable" v-if="editable"
class="button-unstyled edit-button" class="button-unstyled edit-button"
@click="editingName = !editingName"
:title="$t('settings.toggle_edit')" :title="$t('settings.toggle_edit')"
@click="editingName = !editingName"
> >
<FAIcon <FAIcon
class="icon" class="icon"
@ -296,34 +293,6 @@
:user="user" :user="user"
/> />
</template> </template>
<button
v-if="editable"
:disabled="somethingToSave"
class="btn button-default reset-profile-button"
@click="resetState"
>
{{ $t('settings.reset') }}
<FAIcon
fixed-width
class="icon"
icon="clock-rotate-left"
:title="$t('user_card.edit_profile')"
/>
</button>
<button
v-if="editable"
:disabled="somethingToSave"
class="btn button-default save-profile-button"
@click="updateProfile"
>
{{ $t('settings.save') }}
<FAIcon
fixed-width
class="icon"
icon="save"
:title="$t('user_card.edit_profile')"
/>
</button>
</div> </div>
<div <div
v-if="!loggedIn && user.is_local" v-if="!loggedIn && user.is_local"
@ -333,41 +302,6 @@
</div> </div>
</div> </div>
</div> </div>
<template v-if="editable">
<h4>{{ $t('settings.user_preferences') }}</h4>
<p
v-if="role === 'admin' || role === 'moderator'"
class="user-card-setting"
>
<Checkbox v-model="newShowRole">
<template v-if="role === 'admin'">
{{ $t('settings.show_admin_badge') }}
</template>
<template v-if="role === 'moderator'">
{{ $t('settings.show_moderator_badge') }}
</template>
</Checkbox>
</p>
<p class="user-card-setting">
<label>
{{ $t('settings.actor_type') }}
<Select v-model="newActorType">
<option
v-for="option in availableActorTypes"
:key="option"
:value="option"
>
{{ $t('settings.actor_type_' + (option === 'Person' ? 'person_proper' : option)) }}
</option>
</Select>
<div v-if="groupActorAvailable">
<small>
{{ $t('settings.actor_type_description') }}
</small>
</div>
</label>
</p>
</template>
<div <div
v-if="!editable && loggedIn && isOtherUser && (hasNote || !hideBio) && !mergedConfig.userCardHidePersonalMarks" v-if="!editable && loggedIn && isOtherUser && (hasNote || !hideBio) && !mergedConfig.userCardHidePersonalMarks"
class="personal-marks" class="personal-marks"
@ -413,10 +347,11 @@
/> />
</div> </div>
</div> </div>
<h4 v-if="editable"> <h3 v-if="editable">
<span> <span>
{{ $t('settings.bio') }} {{ $t('settings.bio') }}
</span> </span>
{{ ' ' }}
<button <button
class="button-default" class="button-default"
@click="editingBio = !editingBio" @click="editingBio = !editingBio"
@ -427,7 +362,7 @@
icon="pencil" icon="pencil"
/> />
</button> </button>
</h4> </h3>
<template v-if="!editable || !editingBio"> <template v-if="!editable || !editingBio">
<RichContent <RichContent
v-if="!hideBio" v-if="!hideBio"
@ -456,10 +391,11 @@
</template> </template>
</EmojiInput> </EmojiInput>
</template> </template>
<h4 v-if="editable"> <h3 v-if="editable">
<span> <span>
{{ $t('settings.profile_fields.label') }} {{ $t('settings.profile_fields.label') }}
</span> </span>
{{ ' ' }}
<button <button
class="button-default" class="button-default"
@click="editingFields = !editingFields" @click="editingFields = !editingFields"
@ -470,7 +406,7 @@
icon="pencil" icon="pencil"
/> />
</button> </button>
</h4> </h3>
<template v-if="!editable || !editingFields"> <template v-if="!editable || !editingFields">
<div <div
v-if="!hideBio && user.fields_html && user.fields_html.length > 0" v-if="!hideBio && user.fields_html && user.fields_html.length > 0"
@ -564,7 +500,10 @@
class="user-profile-field-add add-field button-default -hover-highlight" class="user-profile-field-add add-field button-default -hover-highlight"
@click="addField" @click="addField"
> >
<FAIcon icon="plus" class="icon" /> <FAIcon
icon="plus"
class="icon"
/>
<span class="label"> <span class="label">
{{ $t("settings.profile_fields.add_field") }} {{ $t("settings.profile_fields.add_field") }}
</span> </span>
@ -572,8 +511,8 @@
</div> </div>
</template> </template>
<div <div
class="user-extras"
v-if="!hideBio" v-if="!hideBio"
class="user-extras"
> >
<span <span
v-if="!editable && !mergedConfig.hideUserStats" v-if="!editable && !mergedConfig.hideUserStats"
@ -647,6 +586,71 @@
</div> </div>
</template> </template>
</div> </div>
<template v-if="editable">
<h3>{{ $t('settings.profile_other') }}</h3>
<p
v-if="role === 'admin' || role === 'moderator'"
class="user-card-setting"
>
<Checkbox v-model="newShowRole">
<template v-if="role === 'admin'">
{{ $t('settings.show_admin_badge') }}
</template>
<template v-if="role === 'moderator'">
{{ $t('settings.show_moderator_badge') }}
</template>
</Checkbox>
</p>
<p class="user-card-setting">
<label>
{{ $t('settings.actor_type') }}
<Select v-model="newActorType">
<option
v-for="option in availableActorTypes"
:key="option"
:value="option"
>
{{ $t('settings.actor_type_' + (option === 'Person' ? 'person_proper' : option)) }}
</option>
</Select>
<div v-if="groupActorAvailable">
<small>
{{ $t('settings.actor_type_description') }}
</small>
</div>
</label>
</p>
<div class="bottom-buttons">
<button
v-if="editable"
:disabled="!somethingToSave"
class="btn button-default reset-profile-button"
@click="resetState"
>
{{ $t('settings.reset') }}
<FAIcon
fixed-width
class="icon"
icon="clock-rotate-left"
:title="$t('user_card.edit_profile')"
/>
</button>
<button
v-if="editable"
:disabled="!somethingToSave"
class="btn button-default save-profile-button"
@click="updateProfile"
>
{{ $t('settings.save') }}
<FAIcon
fixed-width
class="icon"
icon="save"
:title="$t('user_card.edit_profile')"
/>
</button>
</div>
</template>
<teleport to="#modal"> <teleport to="#modal">
<UserTimedFilterModal <UserTimedFilterModal
ref="timedMuteDialog" ref="timedMuteDialog"
@ -674,12 +678,12 @@
id="pick-image" id="pick-image"
class="button-default btn" class="button-default btn"
type="button" type="button"
@click="() => this.$refs.cropper.pickImage()" @click="() => $refs.cropper.pickImage()"
> >
{{ $t('settings.upload_picture') }} {{ $t('settings.upload_picture') }}
</button> </button>
<p class="visibility-notice"> <p class="visibility-notice">
{{ editImage === 'avatar' ? $t('settings.avatar_size_instruction') : $t('settings.banner_size_instruction' )}} {{ editImage === 'avatar' ? $t('settings.avatar_size_instruction') : $t('settings.banner_size_instruction' ) }}
</p> </p>
<template #footer> <template #footer>
<button <button
@ -687,26 +691,26 @@
type="button" type="button"
@click="editImage = false" @click="editImage = false"
> >
{{ this.$t('image_cropper.cancel') }} {{ $t('image_cropper.cancel') }}
</button> </button>
<button <button
:title="editImage === 'avatar' ? $t('settings.reset_avatar') : $t('settings.reset_banner')" :title="editImage === 'avatar' ? $t('settings.reset_avatar') : $t('settings.reset_banner')"
class="button-default btn reset-button" class="button-default btn reset-button"
@click="resetImage" @click="resetImage"
> >
{{ editImage === 'avatar' ? $t('settings.reset_avatar') : $t('settings.reset_banner' )}} {{ editImage === 'avatar' ? $t('settings.reset_avatar') : $t('settings.reset_banner' ) }}
</button> </button>
<button <button
class="button-default btn" class="button-default btn"
type="button" type="button"
@click="this.$refs.cropper.submit(false)" @click="$refs.cropper.submit(false)"
> >
{{ $t('image_cropper.save_without_cropping') }} {{ $t('image_cropper.save_without_cropping') }}
</button> </button>
<button <button
class="button-default btn" class="button-default btn"
type="button" type="button"
@click="this.$refs.cropper.submit(true)" @click="$refs.cropper.submit(true)"
> >
{{ $t('image_cropper.save') }} {{ $t('image_cropper.save') }}
</button> </button>

View file

@ -29,6 +29,8 @@
} }
.user-info { .user-info {
margin: 0.6em;
.Avatar { .Avatar {
width: 5em; width: 5em;
width: calc(min(5em, 20cqw)); width: calc(min(5em, 20cqw));

View file

@ -27,7 +27,11 @@
/* popover styles load on-demand, so we need to override */ /* popover styles load on-demand, so we need to override */
/* stylelint-disable block-no-empty */ /* stylelint-disable block-no-empty */
.user-popover { .user-popover {
margin-bottom: 0.6em; margin: 0;
.user-info {
margin: 1.2em;
}
.user-identity { .user-identity {
aspect-ratio: unset; aspect-ratio: unset;
@ -40,7 +44,6 @@
&.popover { &.popover {
overflow: hidden; overflow: hidden;
padding: 0.6em;
} }
} }
/* stylelint-enable block-no-empty */ /* stylelint-enable block-no-empty */

View file

@ -129,7 +129,6 @@
<style lang="scss"> <style lang="scss">
.user-profile { .user-profile {
flex: 2; flex: 2;
flex-basis: 500px;
// No sticky header on user profile // No sticky header on user profile
--currentPanelStack: 0; --currentPanelStack: 0;
@ -140,6 +139,10 @@
align-items: center; align-items: center;
padding: 2em; padding: 2em;
} }
.user-info {
margin: 1.2em;
}
} }
.user-profile-placeholder { .user-profile-placeholder {

View file

@ -396,7 +396,7 @@
"save": "Save changes", "save": "Save changes",
"reset": "Reset changes", "reset": "Reset changes",
"security": "Security", "security": "Security",
"toggle_edit": "Toggle edit", "toggle_edit": "Edit",
"change_banner": "Change banner", "change_banner": "Change banner",
"change_avatar": "Change avatar", "change_avatar": "Change avatar",
"setting_changed": "Setting is different from default", "setting_changed": "Setting is different from default",
@ -493,7 +493,7 @@
"avatarRadius": "Avatars", "avatarRadius": "Avatars",
"background": "Background", "background": "Background",
"bio": "Bio", "bio": "Bio",
"user_preferences": "Profile settings", "profile_other": "Other",
"email_language": "Language for receiving emails from the server", "email_language": "Language for receiving emails from the server",
"block_export": "Block export", "block_export": "Block export",
"block_export_button": "Export your blocks to a csv file", "block_export_button": "Export your blocks to a csv file",
@ -664,6 +664,7 @@
"label": "Birthday", "label": "Birthday",
"show_birthday": "Show my birthday" "show_birthday": "Show my birthday"
}, },
"account_profile_edit": "Edit Profile",
"account_privacy": "Privacy", "account_privacy": "Privacy",
"use_contain_fit": "Don't crop the attachment in thumbnails", "use_contain_fit": "Don't crop the attachment in thumbnails",
"name": "Name", "name": "Name",