diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue index f9788d874..a7e98db95 100644 --- a/src/components/emoji_input/emoji_input.vue +++ b/src/components/emoji_input/emoji_input.vue @@ -115,6 +115,7 @@ display: flex; flex-direction: column; position: relative; + display: flex; .emoji-picker-icon { position: absolute; diff --git a/src/components/settings_modal/tabs/profile_tab.js b/src/components/settings_modal/tabs/profile_tab.js index a549a8705..7de50dc4c 100644 --- a/src/components/settings_modal/tabs/profile_tab.js +++ b/src/components/settings_modal/tabs/profile_tab.js @@ -1,12 +1,12 @@ import unescape from 'lodash/unescape' import merge from 'lodash/merge' +import UserCard from 'src/components/user_card/user_card.vue' import ImageCropper from 'src/components/image_cropper/image_cropper.vue' import ScopeSelector from 'src/components/scope_selector/scope_selector.vue' import fileSizeFormatService from 'src/components/../services/file_size_format/file_size_format.js' import ProgressButton from 'src/components/progress_button/progress_button.vue' import EmojiInput from 'src/components/emoji_input/emoji_input.vue' import suggestor from 'src/components/emoji_input/suggestor.js' -import Autosuggest from 'src/components/autosuggest/autosuggest.vue' import Checkbox from 'src/components/checkbox/checkbox.vue' import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue' import Select from 'src/components/select/select.vue' @@ -53,10 +53,10 @@ const ProfileTab = { } }, components: { + UserCard, ScopeSelector, ImageCropper, EmojiInput, - Autosuggest, ProgressButton, Checkbox, BooleanSetting, @@ -88,12 +88,6 @@ const ProfileTab = { userSuggestor () { return suggestor({ store: this.$store }) }, - fieldsLimits () { - return this.$store.state.instance.fieldsLimits - }, - maxFields () { - return this.fieldsLimits ? this.fieldsLimits.maxFields : 0 - }, defaultAvatar () { return this.$store.state.instance.server + this.$store.state.instance.defaultAvatar }, diff --git a/src/components/settings_modal/tabs/profile_tab.scss b/src/components/settings_modal/tabs/profile_tab.scss index 7eda943b7..2588caf38 100644 --- a/src/components/settings_modal/tabs/profile_tab.scss +++ b/src/components/settings_modal/tabs/profile_tab.scss @@ -125,9 +125,4 @@ padding: 0 0.5em; } } - - .birthday-input { - display: block; - margin-bottom: 1em; - } } diff --git a/src/components/settings_modal/tabs/profile_tab.vue b/src/components/settings_modal/tabs/profile_tab.vue index 034034a12..4335a678e 100644 --- a/src/components/settings_modal/tabs/profile_tab.vue +++ b/src/components/settings_modal/tabs/profile_tab.vue @@ -2,6 +2,12 @@
{{ $t('settings.name') }}
{{ $t('settings.birthday.label') }}
{{ $t('settings.profile_fields.label') }}
{{ $t('settings.actor_type') }} diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js index bb36c1255..11ffad439 100644 --- a/src/components/user_card/user_card.js +++ b/src/components/user_card/user_card.js @@ -1,3 +1,6 @@ +import merge from 'lodash/merge' +import unescape from 'lodash/unescape' + import ColorInput from 'src/components/color_input/color_input.vue' import UserAvatar from '../user_avatar/user_avatar.vue' import RemoteFollow from '../remote_follow/remote_follow.vue' @@ -10,11 +13,17 @@ import Select from '../select/select.vue' import UserLink from '../user_link/user_link.vue' import RichContent from 'src/components/rich_content/rich_content.jsx' import UserTimedFilterModal from 'src/components/user_timed_filter_modal/user_timed_filter_modal.vue' +import Checkbox from 'src/components/checkbox/checkbox.vue' +import EmojiInput from 'src/components/emoji_input/emoji_input.vue' + import localeService from 'src/services/locale/locale.service.js' +import suggestor from 'src/components/emoji_input/suggestor.js' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' import { mapGetters } from 'vuex' import { usePostStatusStore } from 'src/stores/post_status' +import { propsToNative } from 'src/services/attributes_helper/attributes_helper.service.js' + import { library } from '@fortawesome/fontawesome-svg-core' import { faBell, @@ -24,13 +33,15 @@ import { faEdit, faTimes, faExpandAlt, - faBirthdayCake + faBirthdayCake, + faSave } from '@fortawesome/free-solid-svg-icons' import { useMediaViewerStore } from '../../stores/media_viewer' import { useInterfaceStore } from '../../stores/interface' library.add( + faSave, faRss, faBell, faSearchPlus, @@ -43,6 +54,7 @@ library.add( export default { props: [ + 'editable', 'userId', 'switcher', 'selected', @@ -55,6 +67,7 @@ export default { ], components: { UserAvatar, + Checkbox, RemoteFollow, ModerationTools, AccountActions, @@ -65,13 +78,27 @@ export default { UserLink, UserNote, UserTimedFilterModal, - ColorInput + ColorInput, + EmojiInput }, data () { + const user = this.$store.state.users.currentUser + return { followRequestInProgress: false, muteExpiryAmount: 0, - muteExpiryUnit: 'minutes' + muteExpiryUnit: 'minutes', + + // Editable stuff + newName: user.name_unescaped, + newActorType: user.actor_type, + newBio: unescape(user.description), + newBirthday: user.birthday, + newShowBirthday: user.show_birthday, + newFields: user.fields.map(field => ({ name: field.name, value: field.value })), + editingFields: false, + newLocked: user.locked, + newShowRole: user.show_role, } }, created () { @@ -114,6 +141,13 @@ export default { const days = Math.ceil((new Date() - new Date(this.user.created_at)) / (60 * 60 * 24 * 1000)) return Math.round(this.user.statuses_count / days) }, + emoji () { + return this.$store.state.instance.customEmoji.map(e => ({ + shortcode: e.displayText, + static_url: e.imageUrl, + url: e.imageUrl + })) + }, userHighlightType: { get () { const data = this.$store.getters.mergedConfig.highlight[this.user.screen_name] @@ -184,6 +218,31 @@ export default { const browserLocale = localeService.internalToBrowserLocale(this.$i18n.locale) return this.user.birthday && new Date(Date.parse(this.user.birthday)).toLocaleDateString(browserLocale, { timeZone: 'UTC', day: 'numeric', month: 'long', year: 'numeric' }) }, + + // Editable stuff + fieldsLimits () { + return this.$store.state.instance.fieldsLimits + }, + maxFields () { + return this.fieldsLimits ? this.fieldsLimits.maxFields : 0 + }, + emojiUserSuggestor () { + return suggestor({ + emoji: [ + ...this.$store.getters.standardEmojiList, + ...this.$store.state.instance.customEmoji + ], + store: this.$store + }) + }, + emojiSuggestor () { + return suggestor({ + emoji: [ + ...this.$store.getters.standardEmojiList, + ...this.$store.state.instance.customEmoji + ] + }) + }, ...mapGetters(['mergedConfig']) }, methods: { @@ -238,6 +297,48 @@ export default { e.preventDefault() this.onAvatarClick() } - } + }, + + // Editable stuff + addField () { + if (this.newFields.length < this.maxFields) { + this.newFields.push({ name: '', value: '' }) + return true + } + return false + }, + deleteField (index) { + this.newFields.splice(index, 1) + }, + propsToNative (props) { + return propsToNative(props) + }, + updateProfile () { + const params = { + note: this.newBio, + locked: this.newLocked, + + // Backend notation. + display_name: this.newName, + fields_attributes: this.newFields.filter(el => el != null), + actor_type: this.actorType, + show_role: this.showRole, + birthday: this.newBirthday || '', + show_birthday: this.showBirthday + } + + if (this.emailLanguage) { + params.language = localeService.internalToBackendLocaleMulti(this.emailLanguage) + } + + this.$store.state.api.backendInteractor + .updateProfile({ params }) + .then((user) => { + this.newFields.splice(user.fields.length) + merge(this.newFields, user.fields) + this.$store.commit('addNewUsers', [user]) + this.$store.commit('setCurrentUser', user) + }) + }, } } diff --git a/src/components/user_card/user_card.scss b/src/components/user_card/user_card.scss index 53f885446..f06ac4c55 100644 --- a/src/components/user_card/user_card.scss +++ b/src/components/user_card/user_card.scss @@ -2,6 +2,17 @@ position: relative; z-index: 1; + // editing headers + h4 { + line-height: 2; + display: flex; + padding: 0 1.0em; + + span { + flex: 1; + } + } + .user-card-inner { padding-bottom: 0; } @@ -179,6 +190,10 @@ padding: 0.6em; margin: -0.6em; + &.save-profile-button { + width: auto; + } + &:hover .icon { color: var(--textFaint); } @@ -400,37 +415,64 @@ .user-profile-fields { margin: 0 0.5em; + --emoji-size: 1.8em; + img { object-fit: contain; vertical-align: middle; max-width: 100%; max-height: 400px; - - &.emoji { - width: 18px; - height: 18px; - } } + .user-profile-field-add, .user-profile-field { display: flex; + align-items: baseline; margin: 0.25em; border: 1px solid var(--border); border-radius: var(--roundness); + line-height: 2em; + } + + .user-profile-field-add { + justify-content: center; + } + + .user-profile-field { + .input { + text-align: inherit; + flex: 1; + } + + .delete-field { + display: inline-block; + text-align: center; + width: 2em; + } .user-profile-field-name, - .user-profile-field-value { + .user-profile-field-value, + .user-profile-field-add { text-overflow: ellipsis; white-space: nowrap; overflow: hidden; box-sizing: border-box; - line-height: 2em; + display: inline-flex; + + &.-edit { + padding: 0; + + input { + font-weight: inherit; + } + } } .user-profile-field-name { flex: 0 1 50%; font-weight: 600; text-align: right; + justify-content: end; color: var(--lightText); min-width: 9em; border-right: 1px solid var(--border); diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue index 9c62359c9..5fbfee3c4 100644 --- a/src/components/user_card/user_card.vue +++ b/src/components/user_card/user_card.vue @@ -27,6 +27,19 @@ />
+ + + {{ ' ' }} + {{ $t("settings.profile_fields.add_field") }} + +