diff --git a/CHANGELOG.md b/CHANGELOG.md
index feabbf06..887588f3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- 'Copy link' button for statuses (in the ellipsis menu)
- Autocomplete domains from list of known instances
- 'Bot' settings option and badge
+- Added profile meta data fields that can be set in profile settings
### Changed
- Registration page no longer requires email if the server is configured not to require it
diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index 2398c43c..2dd8902e 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -216,6 +216,7 @@ const getNodeInfo = async ({ store }) => {
store.dispatch('setInstanceOption', { name: 'avatarlimit', value: parseInt(uploadLimits.avatar) })
store.dispatch('setInstanceOption', { name: 'backgroundlimit', value: parseInt(uploadLimits.background) })
store.dispatch('setInstanceOption', { name: 'bannerlimit', value: parseInt(uploadLimits.banner) })
+ store.dispatch('setInstanceOption', { name: 'fieldsLimits', value: metadata.fieldsLimits })
store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames })
store.dispatch('setInstanceOption', { name: 'postFormats', value: metadata.postFormats })
diff --git a/src/components/emoji_input/emoji_input.js b/src/components/emoji_input/emoji_input.js
index f4c3479c..7974a66d 100644
--- a/src/components/emoji_input/emoji_input.js
+++ b/src/components/emoji_input/emoji_input.js
@@ -431,6 +431,7 @@ const EmojiInput = {
const offsetBottom = offsetTop + offsetHeight
panel.style.top = offsetBottom + 'px'
+ if (!picker) return
picker.$el.style.top = offsetBottom + 'px'
picker.$el.style.bottom = 'auto'
}
diff --git a/src/components/settings_modal/tabs/profile_tab.js b/src/components/settings_modal/tabs/profile_tab.js
index 56e956cd..e6db802d 100644
--- a/src/components/settings_modal/tabs/profile_tab.js
+++ b/src/components/settings_modal/tabs/profile_tab.js
@@ -1,4 +1,5 @@
import unescape from 'lodash/unescape'
+import merge from 'lodash/merge'
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'
@@ -16,6 +17,7 @@ const ProfileTab = {
newLocked: this.$store.state.users.currentUser.locked,
newNoRichText: this.$store.state.users.currentUser.no_rich_text,
newDefaultScope: this.$store.state.users.currentUser.default_scope,
+ newFields: this.$store.state.users.currentUser.fields.map(field => ({ name: field.name, value: field.value })),
hideFollows: this.$store.state.users.currentUser.hide_follows,
hideFollowers: this.$store.state.users.currentUser.hide_followers,
hideFollowsCount: this.$store.state.users.currentUser.hide_follows_count,
@@ -63,6 +65,18 @@ const ProfileTab = {
...this.$store.state.instance.emoji,
...this.$store.state.instance.customEmoji
] })
+ },
+ userSuggestor () {
+ return suggestor({
+ users: this.$store.state.users.users,
+ updateUsersList: (query) => this.$store.dispatch('searchUsers', { query })
+ })
+ },
+ fieldsLimits () {
+ return this.$store.state.instance.fieldsLimits
+ },
+ maxFields () {
+ return this.fieldsLimits ? this.fieldsLimits.maxFields : 0
}
},
methods: {
@@ -75,6 +89,7 @@ const ProfileTab = {
// Backend notation.
/* eslint-disable camelcase */
display_name: this.newName,
+ fields_attributes: this.newFields.filter(el => el != null),
default_scope: this.newDefaultScope,
no_rich_text: this.newNoRichText,
hide_follows: this.hideFollows,
@@ -87,6 +102,8 @@ const ProfileTab = {
show_role: this.showRole
/* eslint-enable camelcase */
} }).then((user) => {
+ this.newFields.splice(user.fields.length)
+ merge(this.newFields, user.fields)
this.$store.commit('addNewUsers', [user])
this.$store.commit('setCurrentUser', user)
})
@@ -94,6 +111,16 @@ const ProfileTab = {
changeVis (visibility) {
this.newDefaultScope = visibility
},
+ addField () {
+ if (this.newFields.length < this.maxFields) {
+ this.newFields.push({ name: '', value: '' })
+ return true
+ }
+ return false
+ },
+ deleteField (index, event) {
+ this.$delete(this.newFields, index)
+ },
uploadFile (slot, e) {
const file = e.target.files[0]
if (!file) { return }
diff --git a/src/components/settings_modal/tabs/profile_tab.scss b/src/components/settings_modal/tabs/profile_tab.scss
index 4aab81eb..b3dcf42c 100644
--- a/src/components/settings_modal/tabs/profile_tab.scss
+++ b/src/components/settings_modal/tabs/profile_tab.scss
@@ -79,4 +79,21 @@
.setting-subitem {
margin-left: 1.75em;
}
+
+ .profile-fields {
+ display: flex;
+
+ &>.emoji-input {
+ flex: 1 1 auto;
+ margin: 0 .2em .5em;
+ }
+
+ &>.icon-container {
+ width: 20px;
+
+ &>.icon-cancel {
+ vertical-align: sub;
+ }
+ }
+ }
}
diff --git a/src/components/settings_modal/tabs/profile_tab.vue b/src/components/settings_modal/tabs/profile_tab.vue
index decdb389..0f9210a6 100644
--- a/src/components/settings_modal/tabs/profile_tab.vue
+++ b/src/components/settings_modal/tabs/profile_tab.vue
@@ -95,6 +95,54 @@
{{ $t('settings.discoverable') }}
+
{{ $t('settings.bot') }}
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 4553d25d..2840904f 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -334,6 +334,12 @@
"loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")",
"mutes_tab": "Mutes",
"play_videos_in_modal": "Play videos in a popup frame",
+ "profile_fields": {
+ "label": "Profile metadata",
+ "add_field": "Add Field",
+ "name": "Label",
+ "value": "Content"
+ },
"use_contain_fit": "Don't crop the attachment in thumbnails",
"name": "Name",
"name_bio": "Name & Bio",
diff --git a/src/i18n/it.json b/src/i18n/it.json
index 6c8be351..7311f0b6 100644
--- a/src/i18n/it.json
+++ b/src/i18n/it.json
@@ -255,7 +255,8 @@
"top_bar": "Barra superiore",
"panel_header": "Titolo pannello",
"badge_notification": "Notifica",
- "popover": "Suggerimenti, menù, sbalzi"
+ "popover": "Suggerimenti, menù, sbalzi",
+ "toggled": "Scambiato"
},
"common_colors": {
"rgbo": "Icone, accenti, medaglie",
diff --git a/src/i18n/nl.json b/src/i18n/nl.json
index af728b6e..15ce5cbe 100644
--- a/src/i18n/nl.json
+++ b/src/i18n/nl.json
@@ -28,7 +28,12 @@
"enable": "Inschakelen",
"confirm": "Bevestigen",
"verify": "Verifiëren",
- "generic_error": "Er is een fout opgetreden"
+ "generic_error": "Er is een fout opgetreden",
+ "peek": "Spiek",
+ "close": "Sluiten",
+ "retry": "Opnieuw proberen",
+ "error_retry": "Probeer het opnieuw",
+ "loading": "Laden…"
},
"login": {
"login": "Log in",
@@ -90,7 +95,7 @@
"text/bbcode": "BBCode"
},
"content_warning": "Onderwerp (optioneel)",
- "default": "Zojuist geland in L.A.",
+ "default": "Tijd voor anime!",
"direct_warning": "Deze post zal enkel zichtbaar zijn voor de personen die genoemd zijn.",
"posting": "Plaatsen",
"scope": {
@@ -377,7 +382,7 @@
"button": "Knop",
"text": "Nog een boel andere {0} en {1}",
"mono": "inhoud",
- "input": "Zojuist geland in L.A.",
+ "input": "Tijd voor anime!",
"faint_link": "handige gebruikershandleiding",
"fine_print": "Lees onze {0} om niets nuttig te leren!",
"header_faint": "Alles komt goed",
@@ -451,7 +456,7 @@
"user_mutes": "Gebruikers",
"useStreamingApi": "Berichten en meldingen in real-time ontvangen",
"useStreamingApiWarning": "(Afgeraden, experimenteel, kan berichten overslaan)",
- "type_domains_to_mute": "Voer domeinen in om te negeren",
+ "type_domains_to_mute": "Zoek domeinen om te negeren",
"upload_a_photo": "Upload een foto",
"fun": "Plezier",
"greentext": "Meme pijlen",
@@ -470,7 +475,8 @@
"frontend_version": "Frontend Versie",
"backend_version": "Backend Versie",
"title": "Versie"
- }
+ },
+ "mutes_and_blocks": "Negeringen en Blokkades"
},
"timeline": {
"collapse": "Inklappen",
@@ -708,7 +714,9 @@
"unpin": "Van profiel losmaken",
"delete": "Status verwijderen",
"repeats": "Herhalingen",
- "favorites": "Favorieten"
+ "favorites": "Favorieten",
+ "thread_muted_and_words": ", heeft woorden:",
+ "thread_muted": "Thread genegeerd"
},
"time": {
"years_short": "{0}j",
diff --git a/src/modules/users.js b/src/modules/users.js
index 5e32bb49..68d02931 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -1,6 +1,6 @@
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
import oauthApi from '../services/new_api/oauth.js'
-import { compact, map, each, merge, last, concat, uniq } from 'lodash'
+import { compact, map, each, mergeWith, last, concat, uniq, isArray } from 'lodash'
import { set } from 'vue'
import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.js'
@@ -10,7 +10,7 @@ export const mergeOrAdd = (arr, obj, item) => {
const oldItem = obj[item.id]
if (oldItem) {
// We already have this, so only merge the new info.
- merge(oldItem, item)
+ mergeWith(oldItem, item, mergeArrayLength)
return { item: oldItem, new: false }
} else {
// This is a new item, prepare it
@@ -23,6 +23,13 @@ export const mergeOrAdd = (arr, obj, item) => {
}
}
+const mergeArrayLength = (oldValue, newValue) => {
+ if (isArray(oldValue) && isArray(newValue)) {
+ oldValue.length = newValue.length
+ return mergeWith(oldValue, newValue, mergeArrayLength)
+ }
+}
+
const getNotificationPermission = () => {
const Notification = window.Notification
@@ -116,7 +123,7 @@ export const mutations = {
},
setCurrentUser (state, user) {
state.lastLoginName = user.screen_name
- state.currentUser = merge(state.currentUser || {}, user)
+ state.currentUser = mergeWith(state.currentUser || {}, user, mergeArrayLength)
},
clearCurrentUser (state) {
state.currentUser = false
diff --git a/test/unit/specs/modules/users.spec.js b/test/unit/specs/modules/users.spec.js
index 670acfc8..dfa5684d 100644
--- a/test/unit/specs/modules/users.spec.js
+++ b/test/unit/specs/modules/users.spec.js
@@ -18,6 +18,42 @@ describe('The users module', () => {
expect(state.users).to.eql([user])
expect(state.users[0].name).to.eql('Dude')
})
+
+ it('merging array field in new information for old users', () => {
+ const state = cloneDeep(defaultState)
+ const user = {
+ id: '1',
+ fields: [
+ { name: 'Label 1', value: 'Content 1' }
+ ]
+ }
+ const firstModUser = {
+ id: '1',
+ fields: [
+ { name: 'Label 2', value: 'Content 2' },
+ { name: 'Label 3', value: 'Content 3' }
+ ]
+ }
+ const secondModUser = {
+ id: '1',
+ fields: [
+ { name: 'Label 4', value: 'Content 4' }
+ ]
+ }
+
+ mutations.addNewUsers(state, [user])
+ expect(state.users[0].fields).to.have.length(1)
+ expect(state.users[0].fields[0].name).to.eql('Label 1')
+
+ mutations.addNewUsers(state, [firstModUser])
+ expect(state.users[0].fields).to.have.length(2)
+ expect(state.users[0].fields[0].name).to.eql('Label 2')
+ expect(state.users[0].fields[1].name).to.eql('Label 3')
+
+ mutations.addNewUsers(state, [secondModUser])
+ expect(state.users[0].fields).to.have.length(1)
+ expect(state.users[0].fields[0].name).to.eql('Label 4')
+ })
})
describe('findUser', () => {