#436: merge develop

This commit is contained in:
dave 2019-03-15 11:28:33 -04:00
commit e5786f32a7
34 changed files with 195 additions and 136 deletions

View file

@ -198,6 +198,7 @@ const getNodeInfo = async ({ store }) => {
store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') }) store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames }) store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames })
store.dispatch('setInstanceOption', { name: 'postFormats', value: metadata.postFormats })
const suggestions = metadata.suggestions const suggestions = metadata.suggestions
store.dispatch('setInstanceOption', { name: 'suggestionsEnabled', value: suggestions.enabled }) store.dispatch('setInstanceOption', { name: 'suggestionsEnabled', value: suggestions.enabled })
@ -212,6 +213,16 @@ const getNodeInfo = async ({ store }) => {
} }
const afterStoreSetup = async ({ store, i18n }) => { const afterStoreSetup = async ({ store, i18n }) => {
if (store.state.config.customTheme) {
// This is a hack to deal with async loading of config.json and themes
// See: style_setter.js, setPreset()
window.themeLoaded = true
store.dispatch('setOption', {
name: 'customTheme',
value: store.state.config.customTheme
})
}
const apiConfig = await getStatusnetConfig({ store }) const apiConfig = await getStatusnetConfig({ store })
const staticConfig = await getStaticConfig() const staticConfig = await getStaticConfig()
await setSettings({ store, apiConfig, staticConfig }) await setSettings({ store, apiConfig, staticConfig })

View file

@ -9,7 +9,7 @@ const BlockCard = {
}, },
computed: { computed: {
user () { user () {
return this.$store.getters.userById(this.userId) return this.$store.getters.findUser(this.userId)
}, },
blocked () { blocked () {
return this.user.statusnet_blocking return this.user.statusnet_blocking

View file

@ -9,7 +9,7 @@ const MuteCard = {
}, },
computed: { computed: {
user () { user () {
return this.$store.getters.userById(this.userId) return this.$store.getters.findUser(this.userId)
}, },
muted () { muted () {
return this.user.muted return this.user.muted

View file

@ -145,11 +145,11 @@ const Status = {
return !!(this.status.in_reply_to_status_id && this.status.in_reply_to_user_id) return !!(this.status.in_reply_to_status_id && this.status.in_reply_to_user_id)
}, },
replyToName () { replyToName () {
const user = this.$store.state.users.usersObject[this.status.in_reply_to_user_id] if (this.status.in_reply_to_screen_name) {
if (user) {
return user.screen_name
} else {
return this.status.in_reply_to_screen_name return this.status.in_reply_to_screen_name
} else {
const user = this.$store.getters.findUser(this.status.in_reply_to_user_id)
return user && user.screen_name
} }
}, },
hideReply () { hideReply () {

View file

@ -23,6 +23,11 @@ const UserAvatar = {
imageLoadError () { imageLoadError () {
this.showPlaceholder = true this.showPlaceholder = true
} }
},
watch: {
src () {
this.showPlaceholder = false
}
} }
} }

View file

@ -15,6 +15,9 @@ export default {
betterShadow: this.$store.state.interface.browserSupport.cssFilter betterShadow: this.$store.state.interface.browserSupport.cssFilter
} }
}, },
created () {
this.$store.dispatch('fetchUserRelationship', this.user.id)
},
computed: { computed: {
classes () { classes () {
return [{ return [{

View file

@ -9,7 +9,7 @@ import withList from '../../hocs/with_list/with_list'
const FollowerList = compose( const FollowerList = compose(
withLoadMore({ withLoadMore({
fetch: (props, $store) => $store.dispatch('addFollowers', props.userId), fetch: (props, $store) => $store.dispatch('addFollowers', props.userId),
select: (props, $store) => get($store.getters.userById(props.userId), 'followers', []), select: (props, $store) => get($store.getters.findUser(props.userId), 'followers', []),
destory: (props, $store) => $store.dispatch('clearFollowers', props.userId), destory: (props, $store) => $store.dispatch('clearFollowers', props.userId),
childPropName: 'entries', childPropName: 'entries',
additionalPropNames: ['userId'] additionalPropNames: ['userId']
@ -20,7 +20,7 @@ const FollowerList = compose(
const FriendList = compose( const FriendList = compose(
withLoadMore({ withLoadMore({
fetch: (props, $store) => $store.dispatch('addFriends', props.userId), fetch: (props, $store) => $store.dispatch('addFriends', props.userId),
select: (props, $store) => get($store.getters.userById(props.userId), 'friends', []), select: (props, $store) => get($store.getters.findUser(props.userId), 'friends', []),
destory: (props, $store) => $store.dispatch('clearFriends', props.userId), destory: (props, $store) => $store.dispatch('clearFriends', props.userId),
childPropName: 'entries', childPropName: 'entries',
additionalPropNames: ['userId'] additionalPropNames: ['userId']
@ -31,28 +31,16 @@ const FriendList = compose(
const UserProfile = { const UserProfile = {
data () { data () {
return { return {
error: false error: false,
fetchedUserId: null
} }
}, },
created () { created () {
this.$store.commit('clearTimeline', { timeline: 'user' })
this.$store.commit('clearTimeline', { timeline: 'favorites' })
this.$store.commit('clearTimeline', { timeline: 'media' })
this.$store.dispatch('startFetching', { timeline: 'user', userId: this.fetchBy })
this.$store.dispatch('startFetching', { timeline: 'media', userId: this.fetchBy })
this.startFetchFavorites()
if (!this.user.id) { if (!this.user.id) {
this.$store.dispatch('fetchUser', this.fetchBy) this.fetchUserId()
.catch((reason) => { .then(() => this.startUp())
const errorMessage = get(reason, 'error.error') } else {
if (errorMessage === 'No user with such user_id') { // Known error this.startUp()
this.error = this.$t('user_profile.profile_does_not_exist')
} else if (errorMessage) {
this.error = errorMessage
} else {
this.error = this.$t('user_profile.profile_loading_error')
}
})
} }
}, },
destroyed () { destroyed () {
@ -69,7 +57,7 @@ const UserProfile = {
return this.$store.state.statuses.timelines.media return this.$store.state.statuses.timelines.media
}, },
userId () { userId () {
return this.$route.params.id || this.user.id return this.$route.params.id || this.user.id || this.fetchedUserId
}, },
userName () { userName () {
return this.$route.params.name || this.user.screen_name return this.$route.params.name || this.user.screen_name
@ -79,10 +67,9 @@ const UserProfile = {
this.userId === this.$store.state.users.currentUser.id this.userId === this.$store.state.users.currentUser.id
}, },
userInStore () { userInStore () {
if (this.isExternal) { const routeParams = this.$route.params
return this.$store.getters.userById(this.userId) // This needs fetchedUserId so that computed will be refreshed when user is fetched
} return this.$store.getters.findUser(this.fetchedUserId || routeParams.name || routeParams.id)
return this.$store.getters.userByName(this.userName)
}, },
user () { user () {
if (this.timeline.statuses[0]) { if (this.timeline.statuses[0]) {
@ -93,9 +80,6 @@ const UserProfile = {
} }
return {} return {}
}, },
fetchBy () {
return this.isExternal ? this.userId : this.userName
},
isExternal () { isExternal () {
return this.$route.name === 'external-user-profile' return this.$route.name === 'external-user-profile'
}, },
@ -109,14 +93,38 @@ const UserProfile = {
methods: { methods: {
startFetchFavorites () { startFetchFavorites () {
if (this.isUs) { if (this.isUs) {
this.$store.dispatch('startFetching', { timeline: 'favorites', userId: this.fetchBy }) this.$store.dispatch('startFetching', { timeline: 'favorites', userId: this.userId })
} }
}, },
fetchUserId () {
let fetchPromise
if (this.userId && !this.$route.params.name) {
fetchPromise = this.$store.dispatch('fetchUser', this.userId)
} else {
fetchPromise = this.$store.dispatch('fetchUser', this.userName)
.then(({ id }) => {
this.fetchedUserId = id
})
}
return fetchPromise
.catch((reason) => {
const errorMessage = get(reason, 'error.error')
if (errorMessage === 'No user with such user_id') { // Known error
this.error = this.$t('user_profile.profile_does_not_exist')
} else if (errorMessage) {
this.error = errorMessage
} else {
this.error = this.$t('user_profile.profile_loading_error')
}
})
.then(() => this.startUp())
},
startUp () { startUp () {
this.$store.dispatch('startFetching', { timeline: 'user', userId: this.fetchBy }) if (this.userId) {
this.$store.dispatch('startFetching', { timeline: 'media', userId: this.fetchBy }) this.$store.dispatch('startFetching', { timeline: 'user', userId: this.userId })
this.$store.dispatch('startFetching', { timeline: 'media', userId: this.userId })
this.startFetchFavorites() this.startFetchFavorites()
}
}, },
cleanUp () { cleanUp () {
this.$store.dispatch('stopFetching', 'user') this.$store.dispatch('stopFetching', 'user')
@ -128,19 +136,19 @@ const UserProfile = {
} }
}, },
watch: { watch: {
userName () { // userId can be undefined if we don't know it yet
if (this.isExternal) { userId (newVal) {
return if (newVal) {
this.cleanUp()
this.startUp()
} }
this.cleanUp()
this.startUp()
}, },
userId () { userName () {
if (!this.isExternal) { if (this.$route.params.name) {
return this.fetchUserId()
this.cleanUp()
this.startUp()
} }
this.cleanUp()
this.startUp()
}, },
$route () { $route () {
this.$refs.tabSwitcher.activateTab(0)() this.$refs.tabSwitcher.activateTab(0)()

View file

@ -11,7 +11,7 @@
:title="$t('user_profile.timeline_title')" :title="$t('user_profile.timeline_title')"
:timeline="timeline" :timeline="timeline"
:timeline-name="'user'" :timeline-name="'user'"
:user-id="fetchBy" :user-id="userId"
/> />
<div :label="$t('user_card.followees')" v-if="followsTabVisible" :disabled="!user.friends_count"> <div :label="$t('user_card.followees')" v-if="followsTabVisible" :disabled="!user.friends_count">
<FriendList :userId="userId" /> <FriendList :userId="userId" />
@ -25,7 +25,7 @@
:embedded="true" :title="$t('user_card.media')" :embedded="true" :title="$t('user_card.media')"
timeline-name="media" timeline-name="media"
:timeline="media" :timeline="media"
:user-id="fetchBy" :user-id="userId"
/> />
<Timeline <Timeline
v-if="isUs" v-if="isUs"

View file

@ -49,7 +49,7 @@
"account_not_locked_warning_link": "مقفل", "account_not_locked_warning_link": "مقفل",
"attachments_sensitive": "اعتبر المرفقات كلها كمحتوى حساس", "attachments_sensitive": "اعتبر المرفقات كلها كمحتوى حساس",
"content_type": { "content_type": {
"plain_text": "نص صافٍ" "text/plain": "نص صافٍ"
}, },
"content_warning": "الموضوع (اختياري)", "content_warning": "الموضوع (اختياري)",
"default": "وصلت للتوّ إلى لوس أنجلس.", "default": "وصلت للتوّ إلى لوس أنجلس.",

View file

@ -49,7 +49,7 @@
"account_not_locked_warning_link": "bloquejat", "account_not_locked_warning_link": "bloquejat",
"attachments_sensitive": "Marca l'adjunt com a delicat", "attachments_sensitive": "Marca l'adjunt com a delicat",
"content_type": { "content_type": {
"plain_text": "Text pla" "text/plain": "Text pla"
}, },
"content_warning": "Assumpte (opcional)", "content_warning": "Assumpte (opcional)",
"default": "Em sento…", "default": "Em sento…",

View file

@ -71,7 +71,7 @@
"account_not_locked_warning_link": "uzamčen", "account_not_locked_warning_link": "uzamčen",
"attachments_sensitive": "Označovat přílohy jako citlivé", "attachments_sensitive": "Označovat přílohy jako citlivé",
"content_type": { "content_type": {
"plain_text": "Prostý text", "text/plain": "Prostý text",
"text/html": "HTML", "text/html": "HTML",
"text/markdown": "Markdown" "text/markdown": "Markdown"
}, },

View file

@ -55,7 +55,7 @@
"account_not_locked_warning_link": "gesperrt", "account_not_locked_warning_link": "gesperrt",
"attachments_sensitive": "Anhänge als heikel markieren", "attachments_sensitive": "Anhänge als heikel markieren",
"content_type": { "content_type": {
"plain_text": "Nur Text" "text/plain": "Nur Text"
}, },
"content_warning": "Betreff (optional)", "content_warning": "Betreff (optional)",
"default": "Sitze gerade im Hofbräuhaus.", "default": "Sitze gerade im Hofbräuhaus.",

View file

@ -71,7 +71,7 @@
"account_not_locked_warning_link": "ŝlosita", "account_not_locked_warning_link": "ŝlosita",
"attachments_sensitive": "Marki kunsendaĵojn kiel konsternajn", "attachments_sensitive": "Marki kunsendaĵojn kiel konsternajn",
"content_type": { "content_type": {
"plain_text": "Plata teksto" "text/plain": "Plata teksto"
}, },
"content_warning": "Temo (malnepra)", "content_warning": "Temo (malnepra)",
"default": "Ĵus alvenis al la Universala Kongreso!", "default": "Ĵus alvenis al la Universala Kongreso!",

View file

@ -61,7 +61,7 @@
"account_not_locked_warning_link": "bloqueada", "account_not_locked_warning_link": "bloqueada",
"attachments_sensitive": "Contenido sensible", "attachments_sensitive": "Contenido sensible",
"content_type": { "content_type": {
"plain_text": "Texto Plano" "text/plain": "Texto Plano"
}, },
"content_warning": "Tema (opcional)", "content_warning": "Tema (opcional)",
"default": "Acabo de aterrizar en L.A.", "default": "Acabo de aterrizar en L.A.",

View file

@ -60,7 +60,7 @@
"account_not_locked_warning_link": "lukittu", "account_not_locked_warning_link": "lukittu",
"attachments_sensitive": "Merkkaa liitteet arkaluonteisiksi", "attachments_sensitive": "Merkkaa liitteet arkaluonteisiksi",
"content_type": { "content_type": {
"plain_text": "Tavallinen teksti" "text/plain": "Tavallinen teksti"
}, },
"content_warning": "Aihe (valinnainen)", "content_warning": "Aihe (valinnainen)",
"default": "Tulin juuri saunasta.", "default": "Tulin juuri saunasta.",

View file

@ -51,7 +51,7 @@
"account_not_locked_warning_link": "verrouillé", "account_not_locked_warning_link": "verrouillé",
"attachments_sensitive": "Marquer le média comme sensible", "attachments_sensitive": "Marquer le média comme sensible",
"content_type": { "content_type": {
"plain_text": "Texte brut" "text/plain": "Texte brut"
}, },
"content_warning": "Sujet (optionnel)", "content_warning": "Sujet (optionnel)",
"default": "Écrivez ici votre prochain statut.", "default": "Écrivez ici votre prochain statut.",

View file

@ -49,7 +49,7 @@
"account_not_locked_warning_link": "faoi glas", "account_not_locked_warning_link": "faoi glas",
"attachments_sensitive": "Marcáil ceangaltán mar íogair", "attachments_sensitive": "Marcáil ceangaltán mar íogair",
"content_type": { "content_type": {
"plain_text": "Gnáth-théacs" "text/plain": "Gnáth-théacs"
}, },
"content_warning": "Teideal (roghnach)", "content_warning": "Teideal (roghnach)",
"default": "Lá iontach anseo i nGaillimh", "default": "Lá iontach anseo i nGaillimh",

View file

@ -49,7 +49,7 @@
"account_not_locked_warning_link": "נעול", "account_not_locked_warning_link": "נעול",
"attachments_sensitive": "סמן מסמכים מצורפים כלא בטוחים לצפייה", "attachments_sensitive": "סמן מסמכים מצורפים כלא בטוחים לצפייה",
"content_type": { "content_type": {
"plain_text": "טקסט פשוט" "text/plain": "טקסט פשוט"
}, },
"content_warning": "נושא (נתון לבחירה)", "content_warning": "נושא (נתון לבחירה)",
"default": "הרגע נחת ב-ל.א.", "default": "הרגע נחת ב-ל.א.",

View file

@ -175,7 +175,7 @@
"account_not_locked_warning_link": "bloccato", "account_not_locked_warning_link": "bloccato",
"attachments_sensitive": "Segna allegati come sensibili", "attachments_sensitive": "Segna allegati come sensibili",
"content_type": { "content_type": {
"plain_text": "Testo normale" "text/plain": "Testo normale"
}, },
"content_warning": "Oggetto (facoltativo)", "content_warning": "Oggetto (facoltativo)",
"default": "Appena atterrato in L.A.", "default": "Appena atterrato in L.A.",

View file

@ -61,7 +61,7 @@
"account_not_locked_warning_link": "ロックされたアカウント", "account_not_locked_warning_link": "ロックされたアカウント",
"attachments_sensitive": "ファイルをNSFWにする", "attachments_sensitive": "ファイルをNSFWにする",
"content_type": { "content_type": {
"plain_text": "プレーンテキスト" "text/plain": "プレーンテキスト"
}, },
"content_warning": "せつめい (かかなくてもよい)", "content_warning": "せつめい (かかなくてもよい)",
"default": "はねだくうこうに、つきました。", "default": "はねだくうこうに、つきました。",

View file

@ -56,7 +56,7 @@
"account_not_locked_warning_link": "잠김", "account_not_locked_warning_link": "잠김",
"attachments_sensitive": "첨부물을 민감함으로 설정", "attachments_sensitive": "첨부물을 민감함으로 설정",
"content_type": { "content_type": {
"plain_text": "평문" "text/plain": "평문"
}, },
"content_warning": "주제 (필수 아님)", "content_warning": "주제 (필수 아님)",
"default": "LA에 도착!", "default": "LA에 도착!",

View file

@ -49,7 +49,7 @@
"account_not_locked_warning_link": "låst", "account_not_locked_warning_link": "låst",
"attachments_sensitive": "Merk vedlegg som sensitive", "attachments_sensitive": "Merk vedlegg som sensitive",
"content_type": { "content_type": {
"plain_text": "Klar tekst" "text/plain": "Klar tekst"
}, },
"content_warning": "Tema (valgfritt)", "content_warning": "Tema (valgfritt)",
"default": "Landet akkurat i L.A.", "default": "Landet akkurat i L.A.",

View file

@ -57,7 +57,7 @@
"account_not_locked_warning_link": "gesloten", "account_not_locked_warning_link": "gesloten",
"attachments_sensitive": "Markeer bijlage als gevoelig", "attachments_sensitive": "Markeer bijlage als gevoelig",
"content_type": { "content_type": {
"plain_text": "Gewone tekst" "text/plain": "Gewone tekst"
}, },
"content_warning": "Onderwerp (optioneel)", "content_warning": "Onderwerp (optioneel)",
"default": "Tijd voor een pauze!", "default": "Tijd voor een pauze!",

View file

@ -71,7 +71,7 @@
"account_not_locked_warning_link": "clavat", "account_not_locked_warning_link": "clavat",
"attachments_sensitive": "Marcar las pèças juntas coma sensiblas", "attachments_sensitive": "Marcar las pèças juntas coma sensiblas",
"content_type": { "content_type": {
"plain_text": "Tèxte brut" "text/plain": "Tèxte brut"
}, },
"content_warning": "Avís de contengut (opcional)", "content_warning": "Avís de contengut (opcional)",
"default": "Escrivètz aquí vòstre estatut.", "default": "Escrivètz aquí vòstre estatut.",

View file

@ -71,7 +71,7 @@
"account_not_locked_warning_link": "fechada", "account_not_locked_warning_link": "fechada",
"attachments_sensitive": "Marcar anexos como sensíveis", "attachments_sensitive": "Marcar anexos como sensíveis",
"content_type": { "content_type": {
"plain_text": "Texto puro" "text/plain": "Texto puro"
}, },
"content_warning": "Assunto (opcional)", "content_warning": "Assunto (opcional)",
"default": "Acabei de chegar no Rio!", "default": "Acabei de chegar no Rio!",

View file

@ -49,7 +49,7 @@
"account_not_locked_warning_link": "上锁", "account_not_locked_warning_link": "上锁",
"attachments_sensitive": "标记附件为敏感内容", "attachments_sensitive": "标记附件为敏感内容",
"content_type": { "content_type": {
"plain_text": "纯文本" "text/plain": "纯文本"
}, },
"content_warning": "主题(可选)", "content_warning": "主题(可选)",
"default": "刚刚抵达上海", "default": "刚刚抵达上海",

View file

@ -60,18 +60,6 @@ export default function createPersistedState ({
merge({}, store.state, savedState) merge({}, store.state, savedState)
) )
} }
if (store.state.config.customTheme) {
// This is a hack to deal with async loading of config.json and themes
// See: style_setter.js, setPreset()
window.themeLoaded = true
store.dispatch('setOption', {
name: 'customTheme',
value: store.state.config.customTheme
})
}
if (store.state.oauth.token) {
store.dispatch('loginUser', store.state.oauth.token)
}
loaded = true loaded = true
} catch (e) { } catch (e) {
console.log("Couldn't load state") console.log("Couldn't load state")

View file

@ -1,4 +1,4 @@
import { remove, slice, each, find, maxBy, minBy, merge, first, last, isArray } from 'lodash' import { remove, slice, each, find, maxBy, minBy, merge, first, last, isArray, omitBy } from 'lodash'
import apiService from '../services/api/api.service.js' import apiService from '../services/api/api.service.js'
// import parse from '../services/status_parser/status_parser.js' // import parse from '../services/status_parser/status_parser.js'
@ -72,7 +72,9 @@ const mergeOrAdd = (arr, obj, item) => {
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) // We ignore null values to avoid overwriting existing properties with missing data
// we also skip 'user' because that is handled by users module
merge(oldItem, omitBy(item, (v, k) => v === null || k === 'user'))
// Reactivity fix. // Reactivity fix.
oldItem.attachments.splice(oldItem.attachments.length) oldItem.attachments.splice(oldItem.attachments.length)
return {item: oldItem, new: false} return {item: oldItem, new: false}

View file

@ -18,7 +18,7 @@ export const mergeOrAdd = (arr, obj, item) => {
arr.push(item) arr.push(item)
obj[item.id] = item obj[item.id] = item
if (item.screen_name && !item.screen_name.includes('@')) { if (item.screen_name && !item.screen_name.includes('@')) {
obj[item.screen_name] = item obj[item.screen_name.toLowerCase()] = item
} }
return { item, new: true } return { item, new: true }
} }
@ -91,6 +91,17 @@ export const mutations = {
addNewUsers (state, users) { addNewUsers (state, users) {
each(users, (user) => mergeOrAdd(state.users, state.usersObject, user)) each(users, (user) => mergeOrAdd(state.users, state.usersObject, user))
}, },
updateUserRelationship (state, relationships) {
relationships.forEach((relationship) => {
const user = state.usersObject[relationship.id]
if (user) {
user.follows_you = relationship.followed_by
user.following = relationship.following
user.muted = relationship.muting
user.statusnet_blocking = relationship.blocking
}
})
},
saveBlocks (state, blockIds) { saveBlocks (state, blockIds) {
state.currentUser.blockIds = blockIds state.currentUser.blockIds = blockIds
}, },
@ -122,12 +133,14 @@ export const mutations = {
} }
export const getters = { export const getters = {
userById: state => id => findUser: state => query => {
state.users.find(user => user.id === id), const result = state.usersObject[query]
userByName: state => name => // In case it's a screen_name, we can try searching case-insensitive
state.users.find(user => user.screen_name && if (!result && typeof query === 'string') {
(user.screen_name.toLowerCase() === name.toLowerCase()) return state.usersObject[query.toLowerCase()]
) }
return result
}
} }
export const defaultState = { export const defaultState = {
@ -147,7 +160,14 @@ const users = {
actions: { actions: {
fetchUser (store, id) { fetchUser (store, id) {
return store.rootState.api.backendInteractor.fetchUser({ id }) return store.rootState.api.backendInteractor.fetchUser({ id })
.then((user) => store.commit('addNewUsers', [user])) .then((user) => {
store.commit('addNewUsers', [user])
return user
})
},
fetchUserRelationship (store, id) {
return store.rootState.api.backendInteractor.fetchUserRelationship({ id })
.then((relationships) => store.commit('updateUserRelationship', relationships))
}, },
fetchBlocks (store) { fetchBlocks (store) {
return store.rootState.api.backendInteractor.fetchBlocks() return store.rootState.api.backendInteractor.fetchBlocks()

View file

@ -28,11 +28,9 @@ const BG_UPDATE_URL = '/api/qvitter/update_background_image.json'
const BANNER_UPDATE_URL = '/api/account/update_profile_banner.json' const BANNER_UPDATE_URL = '/api/account/update_profile_banner.json'
const PROFILE_UPDATE_URL = '/api/account/update_profile.json' const PROFILE_UPDATE_URL = '/api/account/update_profile.json'
const EXTERNAL_PROFILE_URL = '/api/externalprofile/show.json' const EXTERNAL_PROFILE_URL = '/api/externalprofile/show.json'
const QVITTER_USER_TIMELINE_URL = '/api/qvitter/statuses/user_timeline.json'
const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json' const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json'
const BLOCKING_URL = '/api/blocks/create.json' const BLOCKING_URL = '/api/blocks/create.json'
const UNBLOCKING_URL = '/api/blocks/destroy.json' const UNBLOCKING_URL = '/api/blocks/destroy.json'
const USER_URL = '/api/users/show.json'
const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import' const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import'
const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account' const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account'
const CHANGE_PASSWORD_URL = '/api/pleroma/change_password' const CHANGE_PASSWORD_URL = '/api/pleroma/change_password'
@ -43,6 +41,9 @@ const SUGGESTIONS_URL = '/api/v1/suggestions'
const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites' const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites'
const MASTODON_USER_NOTIFICATIONS_URL = '/api/v1/notifications' const MASTODON_USER_NOTIFICATIONS_URL = '/api/v1/notifications'
const MASTODON_USER_URL = '/api/v1/accounts'
const MASTODON_USER_RELATIONSHIPS_URL = '/api/v1/accounts/relationships'
const MASTODON_USER_TIMELINE_URL = id => `/api/v1/accounts/${id}/statuses`
import { each, map } from 'lodash' import { each, map } from 'lodash'
import { parseStatus, parseUser, parseNotification } from '../entity_normalizer/entity_normalizer.service.js' import { parseStatus, parseUser, parseNotification } from '../entity_normalizer/entity_normalizer.service.js'
@ -243,7 +244,7 @@ const denyUser = ({id, credentials}) => {
} }
const fetchUser = ({id, credentials}) => { const fetchUser = ({id, credentials}) => {
let url = `${USER_URL}?user_id=${id}` let url = `${MASTODON_USER_URL}/${id}`
return fetch(url, { headers: authHeaders(credentials) }) return fetch(url, { headers: authHeaders(credentials) })
.then((response) => { .then((response) => {
return new Promise((resolve, reject) => response.json() return new Promise((resolve, reject) => response.json()
@ -257,6 +258,20 @@ const fetchUser = ({id, credentials}) => {
.then((data) => parseUser(data)) .then((data) => parseUser(data))
} }
const fetchUserRelationship = ({id, credentials}) => {
let url = `${MASTODON_USER_RELATIONSHIPS_URL}/?id=${id}`
return fetch(url, { headers: authHeaders(credentials) })
.then((response) => {
return new Promise((resolve, reject) => response.json()
.then((json) => {
if (!response.ok) {
return reject(new StatusCodeError(response.status, json, { url }, response))
}
return resolve(json)
}))
})
}
const fetchFriends = ({id, page, credentials}) => { const fetchFriends = ({id, page, credentials}) => {
let url = `${FRIENDS_URL}?user_id=${id}` let url = `${FRIENDS_URL}?user_id=${id}`
if (page) { if (page) {
@ -347,8 +362,8 @@ const fetchTimeline = ({timeline, credentials, since = false, until = false, use
dms: DM_TIMELINE_URL, dms: DM_TIMELINE_URL,
notifications: MASTODON_USER_NOTIFICATIONS_URL, notifications: MASTODON_USER_NOTIFICATIONS_URL,
'publicAndExternal': PUBLIC_AND_EXTERNAL_TIMELINE_URL, 'publicAndExternal': PUBLIC_AND_EXTERNAL_TIMELINE_URL,
user: QVITTER_USER_TIMELINE_URL, user: MASTODON_USER_TIMELINE_URL,
media: QVITTER_USER_TIMELINE_URL, media: MASTODON_USER_TIMELINE_URL,
favorites: MASTODON_USER_FAVORITES_TIMELINE_URL, favorites: MASTODON_USER_FAVORITES_TIMELINE_URL,
tag: TAG_TIMELINE_URL tag: TAG_TIMELINE_URL
} }
@ -357,15 +372,16 @@ const fetchTimeline = ({timeline, credentials, since = false, until = false, use
let url = timelineUrls[timeline] let url = timelineUrls[timeline]
if (timeline === 'user' || timeline === 'media') {
url = url(userId)
}
if (since) { if (since) {
params.push(['since_id', since]) params.push(['since_id', since])
} }
if (until) { if (until) {
params.push(['max_id', until]) params.push(['max_id', until])
} }
if (userId) {
params.push(['user_id', userId])
}
if (tag) { if (tag) {
url += `/${tag}.json` url += `/${tag}.json`
} }
@ -588,6 +604,7 @@ const apiService = {
blockUser, blockUser,
unblockUser, unblockUser,
fetchUser, fetchUser,
fetchUserRelationship,
favorite, favorite,
unfavorite, unfavorite,
retweet, retweet,

View file

@ -30,6 +30,10 @@ const backendInteractorService = (credentials) => {
return apiService.fetchUser({id, credentials}) return apiService.fetchUser({id, credentials})
} }
const fetchUserRelationship = ({id}) => {
return apiService.fetchUserRelationship({id, credentials})
}
const followUser = (id) => { const followUser = (id) => {
return apiService.followUser({credentials, id}) return apiService.followUser({credentials, id})
} }
@ -92,6 +96,7 @@ const backendInteractorService = (credentials) => {
blockUser, blockUser,
unblockUser, unblockUser,
fetchUser, fetchUser,
fetchUserRelationship,
fetchAllFollowing, fetchAllFollowing,
verifyCredentials: apiService.verifyCredentials, verifyCredentials: apiService.verifyCredentials,
startFetching, startFetching,

View file

@ -39,10 +39,10 @@ export const parseUser = (data) => {
return output return output
} }
output.name = null // missing // output.name = ??? missing
output.name_html = data.display_name output.name_html = data.display_name
output.description = null // missing // output.description = ??? missing
output.description_html = data.note output.description_html = data.note
// Utilize avatar_static for gif avatars? // Utilize avatar_static for gif avatars?
@ -59,10 +59,14 @@ export const parseUser = (data) => {
output.statusnet_profile_url = data.url output.statusnet_profile_url = data.url
if (data.pleroma) { if (data.pleroma) {
const pleroma = data.pleroma const relationship = data.pleroma.relationship
output.follows_you = pleroma.follows_you
output.statusnet_blocking = pleroma.statusnet_blocking if (relationship) {
output.muted = pleroma.muted output.follows_you = relationship.followed_by
output.following = relationship.following
output.statusnet_blocking = relationship.blocking
output.muted = relationship.muting
}
} }
// TODO: handle is_local // TODO: handle is_local
@ -83,7 +87,7 @@ export const parseUser = (data) => {
output.friends_count = data.friends_count output.friends_count = data.friends_count
output.bot = null // missing // output.bot = ??? missing
output.statusnet_profile_url = data.statusnet_profile_url output.statusnet_profile_url = data.statusnet_profile_url
@ -134,7 +138,7 @@ const parseAttachment = (data) => {
output.meta = data.meta // not present in BE yet output.meta = data.meta // not present in BE yet
} else { } else {
output.mimetype = data.mimetype output.mimetype = data.mimetype
output.meta = null // missing // output.meta = ??? missing
} }
output.url = data.url output.url = data.url
@ -166,7 +170,7 @@ export const parseStatus = (data) => {
output.in_reply_to_user_id = data.in_reply_to_account_id output.in_reply_to_user_id = data.in_reply_to_account_id
// Missing!! fix in UI? // Missing!! fix in UI?
output.in_reply_to_screen_name = null // output.in_reply_to_screen_name = ???
// Not exactly the same but works // Not exactly the same but works
output.statusnet_conversation_id = data.id output.statusnet_conversation_id = data.id
@ -178,8 +182,6 @@ export const parseStatus = (data) => {
output.summary = data.spoiler_text output.summary = data.spoiler_text
output.summary_html = data.spoiler_text output.summary_html = data.spoiler_text
output.external_url = data.url output.external_url = data.url
// TODO: handle is_local
output.is_local = data.pleroma.local output.is_local = data.pleroma.local
} else { } else {
output.favorited = data.favorited output.favorited = data.favorited
@ -271,6 +273,7 @@ export const parseNotification = (data) => {
if (masto) { if (masto) {
output.type = mastoDict[data.type] || data.type output.type = mastoDict[data.type] || data.type
output.seen = null // missing output.seen = null // missing
output.status = output.type === 'follow' output.status = output.type === 'follow'
? parseFollow(data) ? parseFollow(data)

View file

@ -12,9 +12,13 @@ const mutations = {
setError: () => {} setError: () => {}
} }
const actions = {
fetchUser: () => {},
fetchUserByScreenName: () => {}
}
const testGetters = { const testGetters = {
userByName: state => getters.userByName(state.users), findUser: state => getters.findUser(state.users)
userById: state => getters.userById(state.users)
} }
const localUser = { const localUser = {
@ -31,6 +35,7 @@ const extUser = {
const externalProfileStore = new Vuex.Store({ const externalProfileStore = new Vuex.Store({
mutations, mutations,
actions,
getters: testGetters, getters: testGetters,
state: { state: {
api: { api: {
@ -89,7 +94,7 @@ const externalProfileStore = new Vuex.Store({
currentUser: { currentUser: {
credentials: '' credentials: ''
}, },
usersObject: [extUser], usersObject: { 100: extUser },
users: [extUser] users: [extUser]
} }
} }
@ -97,6 +102,7 @@ const externalProfileStore = new Vuex.Store({
const localProfileStore = new Vuex.Store({ const localProfileStore = new Vuex.Store({
mutations, mutations,
actions,
getters: testGetters, getters: testGetters,
state: { state: {
api: { api: {
@ -155,7 +161,7 @@ const localProfileStore = new Vuex.Store({
currentUser: { currentUser: {
credentials: '' credentials: ''
}, },
usersObject: [localUser], usersObject: { 100: localUser, 'testuser': localUser },
users: [localUser] users: [localUser]
} }
} }

View file

@ -34,40 +34,31 @@ describe('The users module', () => {
}) })
}) })
describe('getUserByName', () => { describe('findUser', () => {
it('returns user with matching screen_name', () => { it('returns user with matching screen_name', () => {
const user = { screen_name: 'Guy', id: '1' }
const state = { const state = {
users: [ usersObject: {
{ screen_name: 'Guy', id: '1' } 1: user,
] guy: user
}
} }
const name = 'Guy' const name = 'Guy'
const expected = { screen_name: 'Guy', id: '1' } const expected = { screen_name: 'Guy', id: '1' }
expect(getters.userByName(state)(name)).to.eql(expected) expect(getters.findUser(state)(name)).to.eql(expected)
}) })
it('returns user with matching screen_name with different case', () => {
const state = {
users: [
{ screen_name: 'guy', id: '1' }
]
}
const name = 'Guy'
const expected = { screen_name: 'guy', id: '1' }
expect(getters.userByName(state)(name)).to.eql(expected)
})
})
describe('getUserById', () => {
it('returns user with matching id', () => { it('returns user with matching id', () => {
const user = { screen_name: 'Guy', id: '1' }
const state = { const state = {
users: [ usersObject: {
{ screen_name: 'Guy', id: '1' } 1: user,
] guy: user
}
} }
const id = '1' const id = '1'
const expected = { screen_name: 'Guy', id: '1' } const expected = { screen_name: 'Guy', id: '1' }
expect(getters.userById(state)(id)).to.eql(expected) expect(getters.findUser(state)(id)).to.eql(expected)
}) })
}) })
}) })