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 0c213ea3..01074b8c 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -33,38 +33,39 @@ const preloadFetch = async (request) => { } } -const getStatusnetConfig = async ({ store }) => { +const getInstanceConfig = async ({ store }) => { try { - const res = await preloadFetch('/api/statusnet/config.json') + const res = await preloadFetch('/api/v1/instance') if (res.ok) { const data = await res.json() - const { name, closed: registrationClosed, textlimit, uploadlimit, server, vapidPublicKey, safeDMMentionsEnabled } = data.site + const textlimit = data.max_toot_chars + const vapidPublicKey = data.pleroma.vapid_public_key - store.dispatch('setInstanceOption', { name: 'name', value: name }) - store.dispatch('setInstanceOption', { name: 'registrationOpen', value: (registrationClosed === '0') }) - store.dispatch('setInstanceOption', { name: 'textlimit', value: parseInt(textlimit) }) - store.dispatch('setInstanceOption', { name: 'server', value: server }) - store.dispatch('setInstanceOption', { name: 'safeDM', value: safeDMMentionsEnabled !== '0' }) - - // TODO: default values for this stuff, added if to not make it break on - // my dev config out of the box. - if (uploadlimit) { - store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadlimit.uploadlimit) }) - store.dispatch('setInstanceOption', { name: 'avatarlimit', value: parseInt(uploadlimit.avatarlimit) }) - store.dispatch('setInstanceOption', { name: 'backgroundlimit', value: parseInt(uploadlimit.backgroundlimit) }) - store.dispatch('setInstanceOption', { name: 'bannerlimit', value: parseInt(uploadlimit.bannerlimit) }) - } + store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit }) if (vapidPublicKey) { store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey }) } - - return data.site.pleromafe } else { throw (res) } } catch (error) { - console.error('Could not load statusnet config, potentially fatal') + console.error('Could not load instance config, potentially fatal') + console.error(error) + } +} + +const getBackendProvidedConfig = async ({ store }) => { + try { + const res = await window.fetch('/api/pleroma/frontend_configurations') + if (res.ok) { + const data = await res.json() + return data.pleroma_fe + } else { + throw (res) + } + } catch (error) { + console.error('Could not load backend-provided frontend config, potentially fatal') console.error(error) } } @@ -225,13 +226,23 @@ const getNodeInfo = async ({ store }) => { const data = await res.json() const metadata = data.metadata const features = metadata.features + store.dispatch('setInstanceOption', { name: 'name', value: metadata.nodeName }) + store.dispatch('setInstanceOption', { name: 'registrationOpen', value: data.openRegistrations }) store.dispatch('setInstanceOption', { name: 'mediaProxyAvailable', value: features.includes('media_proxy') }) + store.dispatch('setInstanceOption', { name: 'safeDM', value: features.includes('safe_dm_mentions') }) store.dispatch('setInstanceOption', { name: 'chatAvailable', value: features.includes('chat') }) store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') }) store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') }) store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits }) store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled }) + const uploadLimits = metadata.uploadLimits + store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) }) + 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 }) @@ -282,7 +293,7 @@ const getNodeInfo = async ({ store }) => { const setConfig = async ({ store }) => { // apiConfig, staticConfig - const configInfos = await Promise.all([getStatusnetConfig({ store }), getStaticConfig()]) + const configInfos = await Promise.all([getBackendProvidedConfig({ store }), getStaticConfig()]) const apiConfig = configInfos[0] const staticConfig = configInfos[1] @@ -305,6 +316,11 @@ const checkOAuthToken = async ({ store }) => { const afterStoreSetup = async ({ store, i18n }) => { const width = windowWidth() store.dispatch('setMobileLayout', width <= 800) + + const overrides = window.___pleromafe_dev_overrides || {} + const server = (typeof overrides.target !== 'undefined') ? overrides.target : window.location.origin + store.dispatch('setInstanceOption', { name: 'server', value: server }) + await setConfig({ store }) const { customTheme, customThemeSource } = store.state.config @@ -329,7 +345,8 @@ const afterStoreSetup = async ({ store, i18n }) => { getTOS({ store }), getInstancePanel({ store }), getStickers({ store }), - getNodeInfo({ store }) + getNodeInfo({ store }), + getInstanceConfig({ store }) ]) // Start fetching things that don't need to block the UI 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.profile_fields.label') }}
+