From 9923ff587b49486cf2e8ba5bdcb717db5d8dda84 Mon Sep 17 00:00:00 2001 From: Sol Fisher Romanoff <sol@solfisher.com> Date: Wed, 22 Jun 2022 18:46:47 +0300 Subject: [PATCH 1/5] Add private note field to user profile --- src/components/user_profile/user_profile.js | 12 ++++++++++-- src/components/user_profile/user_profile.vue | 10 ++++++++++ src/i18n/en.json | 1 + src/modules/users.js | 8 ++++++++ src/services/api/api.service.js | 16 +++++++++++++++- 5 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js index f779b823..3ab1adba 100644 --- a/src/components/user_profile/user_profile.js +++ b/src/components/user_profile/user_profile.js @@ -40,7 +40,8 @@ const UserProfile = { error: false, userId: null, tab: defaultTabKey, - footerRef: null + footerRef: null, + note: null } }, created () { @@ -110,9 +111,13 @@ const UserProfile = { const user = this.$store.getters.findUser(userNameOrId) if (user) { loadById(user.id) + this.note = user.relationship.note } else { this.$store.dispatch('fetchUser', userNameOrId) - .then(({ id }) => loadById(id)) + .then(({ id, relationship }) => { + this.note = relationship.note + return loadById(id) + }) .catch((reason) => { const errorMessage = get(reason, 'error.error') if (errorMessage === 'No user with such user_id') { // Known error @@ -145,6 +150,9 @@ const UserProfile = { if (target.tagName === 'A') { window.open(target.href, '_blank') } + }, + setNote () { + this.$store.dispatch('setNote', { id: this.userId, note: this.note }) } }, watch: { diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue index b1a20269..602c002c 100644 --- a/src/components/user_profile/user_profile.vue +++ b/src/components/user_profile/user_profile.vue @@ -40,6 +40,12 @@ </dd> </dl> </div> + <textarea + v-model="note" + class="note resize-height" + :placeholder="$t('user_card.note')" + @input="setNote" + /> <tab-switcher :active-tab="tab" :render-only-focused="true" @@ -202,6 +208,10 @@ align-items: middle; padding: 2em; } + + .note { + margin: 0.5em 0.75em; + } } .user-profile-placeholder { .panel-body { diff --git a/src/i18n/en.json b/src/i18n/en.json index 3430620b..d17310d6 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -845,6 +845,7 @@ "domain_muted": "Unblock domain", "mute_domain": "Block domain", "bot": "Bot", + "note": "Private note", "admin_menu": { "moderation": "Moderation", "grant_admin": "Grant Admin", diff --git a/src/modules/users.js b/src/modules/users.js index 319b6b18..12eb621f 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -102,6 +102,11 @@ const unmuteDomain = (store, domain) => { .then(() => store.commit('removeDomainMute', domain)) } +const setNote = (store, { id, note }) => { + return store.rootState.api.backendInteractor.setNote({ id, note }) + .then((relationship) => store.commit('updateUserRelationship', [relationship])) +} + export const mutations = { tagUser (state, { user: { id }, tag }) { const user = state.usersObject[id] @@ -366,6 +371,9 @@ const users = { unmuteDomains (store, domain = []) { return Promise.all(domain.map(domain => unmuteDomain(store, domain))) }, + setNote (store, { id, note }) { + return setNote(store, { id, note }) + }, fetchFriends ({ rootState, commit }, id) { const user = rootState.users.usersObject[id] const maxId = last(user.friendIds) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index fa3439e9..dee5e111 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -63,6 +63,7 @@ const MASTODON_MUTE_USER_URL = id => `/api/v1/accounts/${id}/mute` const MASTODON_UNMUTE_USER_URL = id => `/api/v1/accounts/${id}/unmute` const MASTODON_SUBSCRIBE_USER = id => `/api/v1/pleroma/accounts/${id}/subscribe` const MASTODON_UNSUBSCRIBE_USER = id => `/api/v1/pleroma/accounts/${id}/unsubscribe` +const MASTODON_SET_NOTE_URL = id => `/api/v1/accounts/${id}/note` const MASTODON_BOOKMARK_STATUS_URL = id => `/api/v1/statuses/${id}/bookmark` const MASTODON_UNBOOKMARK_STATUS_URL = id => `/api/v1/statuses/${id}/unbookmark` const MASTODON_POST_STATUS_URL = '/api/v1/statuses' @@ -310,7 +311,7 @@ const denyUser = ({ id, credentials }) => { } const fetchUser = ({ id, credentials }) => { - let url = `${MASTODON_USER_URL}/${id}` + let url = `${MASTODON_USER_URL}/${id}` + '?with_relationships=true' return promisedRequest({ url, credentials }) .then((data) => parseUser(data)) } @@ -948,6 +949,18 @@ const unmuteUser = ({ id, credentials }) => { return promisedRequest({ url: MASTODON_UNMUTE_USER_URL(id), credentials, method: 'POST' }) } +const setNote = ({ id, note, credentials }) => { + const form = new FormData() + + form.append('comment', note) + + return fetch(MASTODON_SET_NOTE_URL(id), { + body: form, + headers: authHeaders(credentials), + method: 'POST' + }).then((data) => data.json()) +} + const fetchMascot = ({ credentials }) => { return promisedRequest({ url: MASTODON_MASCOT_URL, credentials }) } @@ -1405,6 +1418,7 @@ const apiService = { fetchMutes, muteUser, unmuteUser, + setNote, subscribeUser, unsubscribeUser, fetchBlocks, From fb2fc686b1fade7165fa595a56f025aeebceaadc Mon Sep 17 00:00:00 2001 From: Sol Fisher Romanoff <sol@solfisher.com> Date: Wed, 22 Jun 2022 19:42:27 +0300 Subject: [PATCH 2/5] Add debounce to API request --- src/components/user_profile/user_profile.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js index 3ab1adba..3390e732 100644 --- a/src/components/user_profile/user_profile.js +++ b/src/components/user_profile/user_profile.js @@ -7,6 +7,7 @@ import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx' import RichContent from 'src/components/rich_content/rich_content.jsx' import List from '../list/list.vue' import withLoadMore from '../../hocs/with_load_more/with_load_more' +import { debounce } from 'lodash' import { library } from '@fortawesome/fontawesome-svg-core' import { faCircleNotch @@ -151,9 +152,9 @@ const UserProfile = { window.open(target.href, '_blank') } }, - setNote () { + setNote: debounce(function () { this.$store.dispatch('setNote', { id: this.userId, note: this.note }) - } + }, 1500) }, watch: { '$route.params.id': function (newVal) { From 4f0eabbd5526472427e33f179d277690427b2831 Mon Sep 17 00:00:00 2001 From: Sol Fisher Romanoff <sol@solfisher.com> Date: Thu, 23 Jun 2022 15:02:43 +0300 Subject: [PATCH 3/5] api: turn MASTODON_USER_URL into a function --- src/services/api/api.service.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index dee5e111..b9be5eb0 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -47,7 +47,7 @@ const MASTODON_PUBLIC_TIMELINE = '/api/v1/timelines/public' const MASTODON_USER_HOME_TIMELINE_URL = '/api/v1/timelines/home' const MASTODON_STATUS_URL = id => `/api/v1/statuses/${id}` const MASTODON_STATUS_CONTEXT_URL = id => `/api/v1/statuses/${id}/context` -const MASTODON_USER_URL = '/api/v1/accounts' +const MASTODON_USER_URL = id => `/api/v1/accounts/${id}?with_relationships=true` const MASTODON_USER_RELATIONSHIPS_URL = '/api/v1/accounts/relationships' const MASTODON_USER_TIMELINE_URL = id => `/api/v1/accounts/${id}/statuses` const MASTODON_LIST_URL = id => `/api/v1/lists/${id}` @@ -311,7 +311,7 @@ const denyUser = ({ id, credentials }) => { } const fetchUser = ({ id, credentials }) => { - let url = `${MASTODON_USER_URL}/${id}` + '?with_relationships=true' + const url = MASTODON_USER_URL(id) return promisedRequest({ url, credentials }) .then((data) => parseUser(data)) } From 398b2624c83f65cb627546fce91508f623a51c22 Mon Sep 17 00:00:00 2001 From: Sol Fisher Romanoff <sol@solfisher.com> Date: Thu, 23 Jun 2022 15:04:19 +0300 Subject: [PATCH 4/5] Add note saving indicator --- src/components/user_profile/user_profile.js | 10 ++++-- src/components/user_profile/user_profile.vue | 36 ++++++++++++++++---- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js index 3390e732..b4c604f8 100644 --- a/src/components/user_profile/user_profile.js +++ b/src/components/user_profile/user_profile.js @@ -42,7 +42,8 @@ const UserProfile = { userId: null, tab: defaultTabKey, footerRef: null, - note: null + note: null, + noteLoading: false } }, created () { @@ -152,8 +153,13 @@ const UserProfile = { window.open(target.href, '_blank') } }, - setNote: debounce(function () { + setNote () { + this.noteLoading = true + this.debounceSetNote() + }, + debounceSetNote: debounce(function () { this.$store.dispatch('setNote', { id: this.userId, note: this.note }) + this.noteLoading = false }, 1500) }, watch: { diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue index 602c002c..5a47cdde 100644 --- a/src/components/user_profile/user_profile.vue +++ b/src/components/user_profile/user_profile.vue @@ -40,12 +40,24 @@ </dd> </dl> </div> - <textarea - v-model="note" - class="note resize-height" - :placeholder="$t('user_card.note')" - @input="setNote" - /> + <div class="note"> + <textarea + v-model="note" + class="resize-height" + :placeholder="$t('user_card.note')" + @input="setNote" + /> + <div + v-show="noteLoading" + class="preview-spinner" + > + <FAIcon + class="fa-old-padding" + spin + icon="circle-notch" + /> + </div> + </div> <tab-switcher :active-tab="tab" :render-only-focused="true" @@ -210,7 +222,19 @@ } .note { + position: relative; margin: 0.5em 0.75em; + + textarea { + width: 100%; + } + + .preview-spinner { + position: absolute; + top: 0; + right: 0; + margin: 0.5em 0.25em; + } } } .user-profile-placeholder { From 231a4af7545e6989506e70bb91aef1aaa31899f7 Mon Sep 17 00:00:00 2001 From: Sol Fisher Romanoff <sol@solfisher.com> Date: Thu, 23 Jun 2022 15:04:54 +0300 Subject: [PATCH 5/5] Disable private note for self --- src/components/user_profile/user_profile.vue | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue index 5a47cdde..7e3599f7 100644 --- a/src/components/user_profile/user_profile.vue +++ b/src/components/user_profile/user_profile.vue @@ -40,7 +40,10 @@ </dd> </dl> </div> - <div class="note"> + <div + v-if="!isUs" + class="note" + > <textarea v-model="note" class="resize-height"