make use of api/v1/instance

This commit is contained in:
Shpuld Shpuldson 2020-06-27 12:26:19 +03:00
commit 5ab62c4cb8
11 changed files with 201 additions and 32 deletions

View file

@ -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) - 'Copy link' button for statuses (in the ellipsis menu)
- Autocomplete domains from list of known instances - Autocomplete domains from list of known instances
- 'Bot' settings option and badge - 'Bot' settings option and badge
- Added profile meta data fields that can be set in profile settings
### Changed ### Changed
- Registration page no longer requires email if the server is configured not to require it - Registration page no longer requires email if the server is configured not to require it

View file

@ -33,38 +33,39 @@ const preloadFetch = async (request) => {
} }
} }
const getStatusnetConfig = async ({ store }) => { const getInstanceConfig = async ({ store }) => {
try { try {
const res = await preloadFetch('/api/statusnet/config.json') const res = await preloadFetch('/api/v1/instance')
if (res.ok) { if (res.ok) {
const data = await res.json() 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: 'textlimit', value: textlimit })
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) })
}
if (vapidPublicKey) { if (vapidPublicKey) {
store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey }) store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey })
} }
return data.site.pleromafe
} else { } else {
throw (res) throw (res)
} }
} catch (error) { } 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) console.error(error)
} }
} }
@ -225,13 +226,23 @@ const getNodeInfo = async ({ store }) => {
const data = await res.json() const data = await res.json()
const metadata = data.metadata const metadata = data.metadata
const features = metadata.features 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: '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: 'chatAvailable', value: features.includes('chat') })
store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') }) store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') }) store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits }) store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits })
store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled }) 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: 'restrictedNicknames', value: metadata.restrictedNicknames })
store.dispatch('setInstanceOption', { name: 'postFormats', value: metadata.postFormats }) store.dispatch('setInstanceOption', { name: 'postFormats', value: metadata.postFormats })
@ -282,7 +293,7 @@ const getNodeInfo = async ({ store }) => {
const setConfig = async ({ store }) => { const setConfig = async ({ store }) => {
// apiConfig, staticConfig // apiConfig, staticConfig
const configInfos = await Promise.all([getStatusnetConfig({ store }), getStaticConfig()]) const configInfos = await Promise.all([getBackendProvidedConfig({ store }), getStaticConfig()])
const apiConfig = configInfos[0] const apiConfig = configInfos[0]
const staticConfig = configInfos[1] const staticConfig = configInfos[1]
@ -305,6 +316,11 @@ const checkOAuthToken = async ({ store }) => {
const afterStoreSetup = async ({ store, i18n }) => { const afterStoreSetup = async ({ store, i18n }) => {
const width = windowWidth() const width = windowWidth()
store.dispatch('setMobileLayout', width <= 800) 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 }) await setConfig({ store })
const { customTheme, customThemeSource } = store.state.config const { customTheme, customThemeSource } = store.state.config
@ -329,7 +345,8 @@ const afterStoreSetup = async ({ store, i18n }) => {
getTOS({ store }), getTOS({ store }),
getInstancePanel({ store }), getInstancePanel({ store }),
getStickers({ store }), getStickers({ store }),
getNodeInfo({ store }) getNodeInfo({ store }),
getInstanceConfig({ store })
]) ])
// Start fetching things that don't need to block the UI // Start fetching things that don't need to block the UI

View file

@ -431,6 +431,7 @@ const EmojiInput = {
const offsetBottom = offsetTop + offsetHeight const offsetBottom = offsetTop + offsetHeight
panel.style.top = offsetBottom + 'px' panel.style.top = offsetBottom + 'px'
if (!picker) return
picker.$el.style.top = offsetBottom + 'px' picker.$el.style.top = offsetBottom + 'px'
picker.$el.style.bottom = 'auto' picker.$el.style.bottom = 'auto'
} }

View file

@ -1,4 +1,5 @@
import unescape from 'lodash/unescape' import unescape from 'lodash/unescape'
import merge from 'lodash/merge'
import ImageCropper from 'src/components/image_cropper/image_cropper.vue' import ImageCropper from 'src/components/image_cropper/image_cropper.vue'
import ScopeSelector from 'src/components/scope_selector/scope_selector.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 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, newLocked: this.$store.state.users.currentUser.locked,
newNoRichText: this.$store.state.users.currentUser.no_rich_text, newNoRichText: this.$store.state.users.currentUser.no_rich_text,
newDefaultScope: this.$store.state.users.currentUser.default_scope, 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, hideFollows: this.$store.state.users.currentUser.hide_follows,
hideFollowers: this.$store.state.users.currentUser.hide_followers, hideFollowers: this.$store.state.users.currentUser.hide_followers,
hideFollowsCount: this.$store.state.users.currentUser.hide_follows_count, hideFollowsCount: this.$store.state.users.currentUser.hide_follows_count,
@ -63,6 +65,18 @@ const ProfileTab = {
...this.$store.state.instance.emoji, ...this.$store.state.instance.emoji,
...this.$store.state.instance.customEmoji ...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: { methods: {
@ -75,6 +89,7 @@ const ProfileTab = {
// Backend notation. // Backend notation.
/* eslint-disable camelcase */ /* eslint-disable camelcase */
display_name: this.newName, display_name: this.newName,
fields_attributes: this.newFields.filter(el => el != null),
default_scope: this.newDefaultScope, default_scope: this.newDefaultScope,
no_rich_text: this.newNoRichText, no_rich_text: this.newNoRichText,
hide_follows: this.hideFollows, hide_follows: this.hideFollows,
@ -87,6 +102,8 @@ const ProfileTab = {
show_role: this.showRole show_role: this.showRole
/* eslint-enable camelcase */ /* eslint-enable camelcase */
} }).then((user) => { } }).then((user) => {
this.newFields.splice(user.fields.length)
merge(this.newFields, user.fields)
this.$store.commit('addNewUsers', [user]) this.$store.commit('addNewUsers', [user])
this.$store.commit('setCurrentUser', user) this.$store.commit('setCurrentUser', user)
}) })
@ -94,6 +111,16 @@ const ProfileTab = {
changeVis (visibility) { changeVis (visibility) {
this.newDefaultScope = 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) { uploadFile (slot, e) {
const file = e.target.files[0] const file = e.target.files[0]
if (!file) { return } if (!file) { return }

View file

@ -79,4 +79,21 @@
.setting-subitem { .setting-subitem {
margin-left: 1.75em; 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;
}
}
}
} }

View file

@ -95,6 +95,54 @@
{{ $t('settings.discoverable') }} {{ $t('settings.discoverable') }}
</Checkbox> </Checkbox>
</p> </p>
<div v-if="maxFields > 0">
<p>{{ $t('settings.profile_fields.label') }}</p>
<div
v-for="(_, i) in newFields"
:key="i"
class="profile-fields"
>
<EmojiInput
v-model="newFields[i].name"
enable-emoji-picker
hide-emoji-button
:suggest="userSuggestor"
>
<input
v-model="newFields[i].name"
:placeholder="$t('settings.profile_fields.name')"
>
</EmojiInput>
<EmojiInput
v-model="newFields[i].value"
enable-emoji-picker
hide-emoji-button
:suggest="userSuggestor"
>
<input
v-model="newFields[i].value"
:placeholder="$t('settings.profile_fields.value')"
>
</EmojiInput>
<div
class="icon-container"
>
<i
v-show="newFields.length > 1"
class="icon-cancel"
@click="deleteField(i)"
/>
</div>
</div>
<a
v-if="newFields.length < maxFields"
class="add-field faint"
@click="addField"
>
<i class="icon-plus" />
{{ $t("settings.profile_fields.add_field") }}
</a>
</div>
<p> <p>
<Checkbox v-model="bot"> <Checkbox v-model="bot">
{{ $t('settings.bot') }} {{ $t('settings.bot') }}

View file

@ -334,6 +334,12 @@
"loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")", "loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")",
"mutes_tab": "Mutes", "mutes_tab": "Mutes",
"play_videos_in_modal": "Play videos in a popup frame", "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", "use_contain_fit": "Don't crop the attachment in thumbnails",
"name": "Name", "name": "Name",
"name_bio": "Name & Bio", "name_bio": "Name & Bio",

View file

@ -255,7 +255,8 @@
"top_bar": "Barra superiore", "top_bar": "Barra superiore",
"panel_header": "Titolo pannello", "panel_header": "Titolo pannello",
"badge_notification": "Notifica", "badge_notification": "Notifica",
"popover": "Suggerimenti, menù, sbalzi" "popover": "Suggerimenti, menù, sbalzi",
"toggled": "Scambiato"
}, },
"common_colors": { "common_colors": {
"rgbo": "Icone, accenti, medaglie", "rgbo": "Icone, accenti, medaglie",

View file

@ -28,7 +28,12 @@
"enable": "Inschakelen", "enable": "Inschakelen",
"confirm": "Bevestigen", "confirm": "Bevestigen",
"verify": "Verifiëren", "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": {
"login": "Log in", "login": "Log in",
@ -90,7 +95,7 @@
"text/bbcode": "BBCode" "text/bbcode": "BBCode"
}, },
"content_warning": "Onderwerp (optioneel)", "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.", "direct_warning": "Deze post zal enkel zichtbaar zijn voor de personen die genoemd zijn.",
"posting": "Plaatsen", "posting": "Plaatsen",
"scope": { "scope": {
@ -377,7 +382,7 @@
"button": "Knop", "button": "Knop",
"text": "Nog een boel andere {0} en {1}", "text": "Nog een boel andere {0} en {1}",
"mono": "inhoud", "mono": "inhoud",
"input": "Zojuist geland in L.A.", "input": "Tijd voor anime!",
"faint_link": "handige gebruikershandleiding", "faint_link": "handige gebruikershandleiding",
"fine_print": "Lees onze {0} om niets nuttig te leren!", "fine_print": "Lees onze {0} om niets nuttig te leren!",
"header_faint": "Alles komt goed", "header_faint": "Alles komt goed",
@ -451,7 +456,7 @@
"user_mutes": "Gebruikers", "user_mutes": "Gebruikers",
"useStreamingApi": "Berichten en meldingen in real-time ontvangen", "useStreamingApi": "Berichten en meldingen in real-time ontvangen",
"useStreamingApiWarning": "(Afgeraden, experimenteel, kan berichten overslaan)", "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", "upload_a_photo": "Upload een foto",
"fun": "Plezier", "fun": "Plezier",
"greentext": "Meme pijlen", "greentext": "Meme pijlen",
@ -470,7 +475,8 @@
"frontend_version": "Frontend Versie", "frontend_version": "Frontend Versie",
"backend_version": "Backend Versie", "backend_version": "Backend Versie",
"title": "Versie" "title": "Versie"
} },
"mutes_and_blocks": "Negeringen en Blokkades"
}, },
"timeline": { "timeline": {
"collapse": "Inklappen", "collapse": "Inklappen",
@ -708,7 +714,9 @@
"unpin": "Van profiel losmaken", "unpin": "Van profiel losmaken",
"delete": "Status verwijderen", "delete": "Status verwijderen",
"repeats": "Herhalingen", "repeats": "Herhalingen",
"favorites": "Favorieten" "favorites": "Favorieten",
"thread_muted_and_words": ", heeft woorden:",
"thread_muted": "Thread genegeerd"
}, },
"time": { "time": {
"years_short": "{0}j", "years_short": "{0}j",

View file

@ -1,6 +1,6 @@
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
import oauthApi from '../services/new_api/oauth.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 { set } from 'vue'
import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.js' import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.js'
@ -10,7 +10,7 @@ export const mergeOrAdd = (arr, obj, item) => {
const oldItem = obj[item.id] const oldItem = obj[item.id]
if (oldItem) { if (oldItem) {
// We already have this, so only merge the new info. // We already have this, so only merge the new info.
merge(oldItem, item) mergeWith(oldItem, item, mergeArrayLength)
return { item: oldItem, new: false } return { item: oldItem, new: false }
} else { } else {
// This is a new item, prepare it // 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 getNotificationPermission = () => {
const Notification = window.Notification const Notification = window.Notification
@ -116,7 +123,7 @@ export const mutations = {
}, },
setCurrentUser (state, user) { setCurrentUser (state, user) {
state.lastLoginName = user.screen_name state.lastLoginName = user.screen_name
state.currentUser = merge(state.currentUser || {}, user) state.currentUser = mergeWith(state.currentUser || {}, user, mergeArrayLength)
}, },
clearCurrentUser (state) { clearCurrentUser (state) {
state.currentUser = false state.currentUser = false

View file

@ -18,6 +18,42 @@ describe('The users module', () => {
expect(state.users).to.eql([user]) expect(state.users).to.eql([user])
expect(state.users[0].name).to.eql('Dude') 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', () => { describe('findUser', () => {