From d0075026290c90d8406c7ac81413259a8ae58ec7 Mon Sep 17 00:00:00 2001 From: Shpuld Shpuldson Date: Fri, 15 Nov 2019 08:39:21 +0200 Subject: add fetching for emoji reactions, draft design --- src/services/api/api.service.js | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src/services/api/api.service.js') diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 8f5eb416..7ef4b74a 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -71,6 +71,7 @@ const MASTODON_MUTE_CONVERSATION = id => `/api/v1/statuses/${id}/mute` const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute` const MASTODON_SEARCH_2 = `/api/v2/search` const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search' +const PLEROMA_EMOJI_REACTIONS_URL = id => `/api/v1/pleroma/statuses/${id}/emoji_reactions_by` const oldfetch = window.fetch @@ -864,6 +865,10 @@ const fetchRebloggedByUsers = ({ id }) => { return promisedRequest({ url: MASTODON_STATUS_REBLOGGEDBY_URL(id) }).then((users) => users.map(parseUser)) } +const fetchEmojiReactions = ({ id }) => { + return promisedRequest({ url: PLEROMA_EMOJI_REACTIONS_URL(id) }) +} + const reportUser = ({ credentials, userId, statusIds, comment, forward }) => { return promisedRequest({ url: MASTODON_REPORT_USER_URL, @@ -997,6 +1002,7 @@ const apiService = { fetchPoll, fetchFavoritedByUsers, fetchRebloggedByUsers, + fetchEmojiReactions, reportUser, updateNotificationSettings, search2, -- cgit v1.2.3-70-g09d2 From 319bb4ac2895b8eb62da42e3f95addc9bb67b1a0 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sun, 24 Nov 2019 18:50:28 +0200 Subject: initial streaming work --- src/modules/api.js | 15 ++++++++ src/modules/users.js | 13 ++++--- src/services/api/api.service.js | 40 ++++++++++++++++++++++ .../backend_interactor_service.js | 15 +++++++- 4 files changed, 77 insertions(+), 6 deletions(-) (limited to 'src/services/api/api.service.js') diff --git a/src/modules/api.js b/src/modules/api.js index 1293e3c8..1bf65db5 100644 --- a/src/modules/api.js +++ b/src/modules/api.js @@ -6,6 +6,7 @@ const api = { backendInteractor: backendInteractorService(), fetchers: {}, socket: null, + mastoSocket: null, followRequests: [] }, mutations: { @@ -29,6 +30,20 @@ const api = { } }, actions: { + startMastoSocket (store) { + store.state.mastoSocket = store.state.backendInteractor + .startUserSocket({ + store, + onMessage: (message) => { + if (!message) return + if (message.event === 'notification') { + store.dispatch('addNewNotifications', { notifications: [message.notification], older: false }) + } else if (message.event === 'update') { + store.dispatch('addNewStatuses', { statuses: [message.status], userId: false, showImmediately: false, timeline: 'friends' }) + } + } + }) + }, startFetchingTimeline (store, { timeline = 'friends', tag = false, userId = false }) { // Don't start fetching if we already are. if (store.state.fetchers[timeline]) return diff --git a/src/modules/users.js b/src/modules/users.js index e1373220..861a2f4f 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -469,11 +469,14 @@ const users = { store.dispatch('initializeSocket') } - // Start getting fresh posts. - store.dispatch('startFetchingTimeline', { timeline: 'friends' }) - - // Start fetching notifications - store.dispatch('startFetchingNotifications') + store.dispatch('startMastoSocket').catch((error) => { + console.error(error) + // Start getting fresh posts. + store.dispatch('startFetchingTimeline', { timeline: 'friends' }) + + // Start fetching notifications + store.dispatch('startFetchingNotifications') + }) // Get user mutes store.dispatch('fetchMutes') diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 8f5eb416..7f27564f 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -71,6 +71,7 @@ const MASTODON_MUTE_CONVERSATION = id => `/api/v1/statuses/${id}/mute` const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute` const MASTODON_SEARCH_2 = `/api/v2/search` const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search' +const MASTODON_STREAMING = '/api/v1/streaming' const oldfetch = window.fetch @@ -932,6 +933,45 @@ const search2 = ({ credentials, q, resolve, limit, offset, following }) => { }) } +export const getMastodonSocketURI = ({ credentials, stream, args = {} }) => { + return Object.entries({ + ...(credentials + ? { access_token: credentials } + : {} + ), + stream, + ...args + }).reduce((acc, [key, val]) => { + return acc + `${key}=${val}&` + }, MASTODON_STREAMING + '?') +} + +const MASTODON_STREAMING_EVENTS = new Set([ + 'update', + 'notification', + 'delete', + 'filters_changed' +]) + +export const handleMastoWS = (wsEvent) => { + console.debug('Event', wsEvent) + const { data } = wsEvent + if (!data) return + const parsedEvent = JSON.parse(data) + const { event, payload } = parsedEvent + if (MASTODON_STREAMING_EVENTS.has(event)) { + const data = payload ? JSON.parse(payload) : null + if (event === 'update') { + return { event, status: parseStatus(data) } + } else if (event === 'notification') { + return { event, notification: parseNotification(data) } + } + } else { + console.warn('Unknown event', wsEvent) + return null + } +} + const apiService = { verifyCredentials, fetchTimeline, diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js index 57fdccde..0cef4640 100644 --- a/src/services/backend_interactor_service/backend_interactor_service.js +++ b/src/services/backend_interactor_service/backend_interactor_service.js @@ -1,4 +1,4 @@ -import apiService from '../api/api.service.js' +import apiService, { getMastodonSocketURI, handleMastoWS } from '../api/api.service.js' import timelineFetcherService from '../timeline_fetcher/timeline_fetcher.service.js' import notificationsFetcher from '../notifications_fetcher/notifications_fetcher.service.js' import followRequestFetcher from '../../services/follow_request_fetcher/follow_request_fetcher.service' @@ -16,6 +16,19 @@ const backendInteractorService = credentials => ({ return followRequestFetcher.startFetching({ store, credentials }) }, + startUserSocket ({ store, onMessage }) { + const serv = store.rootState.instance.server.replace('https', 'wss') + // const serb = 'ws://localhost:8080/' + const url = serv + getMastodonSocketURI({ credentials, stream: 'user' }) + const socket = new WebSocket(url) + console.log(socket) + if (socket) { + socket.addEventListener('message', (wsEvent) => onMessage(handleMastoWS(wsEvent))) + } else { + throw new Error('failed to connect to socket') + } + }, + ...Object.entries(apiService).reduce((acc, [key, func]) => { return { ...acc, -- cgit v1.2.3-70-g09d2 From 13fc2612ae388dec682829ae2b6211bb3cb8ccb3 Mon Sep 17 00:00:00 2001 From: Wyatt Benno Date: Thu, 5 Dec 2019 11:48:37 +0900 Subject: Change 403 messaging --- src/components/timeline/timeline.js | 7 ++++++- src/components/timeline/timeline.vue | 19 ++++++++++++++++--- src/i18n/en.json | 2 ++ src/modules/statuses.js | 7 +++++++ src/services/api/api.service.js | 10 ++++++++-- .../timeline_fetcher/timeline_fetcher.service.js | 6 ++++++ 6 files changed, 45 insertions(+), 6 deletions(-) (limited to 'src/services/api/api.service.js') diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index 27a9a55e..6086336c 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -36,7 +36,12 @@ const Timeline = { } }, computed: { - timelineError () { return this.$store.state.statuses.error }, + timelineError () { + return this.$store.state.statuses.error + }, + error403 () { + return this.$store.state.statuses.error403 + }, newStatusCount () { return this.timeline.newStatusCount }, diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue index 93f6f570..1c45d0f6 100644 --- a/src/components/timeline/timeline.vue +++ b/src/components/timeline/timeline.vue @@ -11,15 +11,22 @@ > {{ $t('timeline.error_fetching') }} +
+ {{ $t('timeline.error_403') }} +
@@ -67,12 +74,18 @@ {{ $t('timeline.no_more_statuses') }}
+ + +
{{ $t('timeline.error_403') }}
@@ -74,17 +74,17 @@ {{ $t('timeline.no_more_statuses') }}
- +
({ notifications: emptyNotifications(), favorites: new Set(), error: false, - error403: false, + errorData: '', timelines: { mentions: emptyTl(), public: emptyTl(), @@ -480,8 +480,8 @@ export const mutations = { setError (state, { value }) { state.error = value }, - set403Error (state, { value }) { - state.error403 = value + setErrorData (state, { value }) { + state.errorData = value }, setNotificationsLoading (state, { value }) { state.notifications.loading = value @@ -532,8 +532,8 @@ const statuses = { setError ({ rootState, commit }, { value }) { commit('setError', { value }) }, - set403Error ({ rootState, commit }, { value }) { - commit('set403Error', { value }) + setErrorData ({ rootState, commit }, { value }) { + commit('setErrorData', { value }) }, setNotificationsLoading ({ rootState, commit }, { value }) { commit('setNotificationsLoading', { value }) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index a2aa802f..45b63caf 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -532,10 +532,7 @@ const fetchTimeline = ({ return fetch(url, { headers: authHeaders(credentials) }) .then((data) => { - if (data.ok || data.status === 403) { - return data - } - throw new Error('Error fetching timeline', data) + return data }) .then((data) => data.json()) .then((data) => { diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js index b7952050..1aaae563 100644 --- a/src/services/timeline_fetcher/timeline_fetcher.service.js +++ b/src/services/timeline_fetcher/timeline_fetcher.service.js @@ -6,7 +6,7 @@ const update = ({ store, statuses, timeline, showImmediately, userId }) => { const ccTimeline = camelCase(timeline) store.dispatch('setError', { value: false }) - store.dispatch('set403Error', { value: false }) + store.dispatch('setErrorData', { value: false }) store.dispatch('addNewStatuses', { timeline: ccTimeline, @@ -46,9 +46,9 @@ const fetchAndUpdate = ({ return apiService.fetchTimeline(args) .then((statuses) => { - // Change messaging if not public if (statuses.error) { - store.dispatch('set403Error', { value: statuses.error }) + console.log(statuses) + store.dispatch('setErrorData', { value: statuses.error }) return } if (!older && statuses.length >= 20 && !timelineData.loading && numStatusesBeforeFetch > 0) { -- cgit v1.2.3-70-g09d2 From 8ee80339555c53d45602f40fdbe6b487a6992515 Mon Sep 17 00:00:00 2001 From: Wyatt Benno Date: Mon, 9 Dec 2019 10:31:57 +0900 Subject: Set error data --- src/components/timeline/timeline.vue | 4 ++-- src/i18n/en.json | 1 - src/modules/statuses.js | 2 +- src/services/api/api.service.js | 7 ++++++- src/services/timeline_fetcher/timeline_fetcher.service.js | 4 ++-- 5 files changed, 11 insertions(+), 7 deletions(-) (limited to 'src/services/api/api.service.js') diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue index d9f4025d..bb4ab379 100644 --- a/src/components/timeline/timeline.vue +++ b/src/components/timeline/timeline.vue @@ -16,7 +16,7 @@ class="loadmore-error alert error" @click.prevent > - {{ $t('timeline.error_403') }} + {{ errorData.statusText }}
@@ -788,19 +790,33 @@ $status-margin: 0.75em; .emoji-reactions { display: flex; - margin-top: 0.75em; + margin-top: 0.25em; + flex-wrap: wrap; } .emoji-reaction { padding: 0 0.5em; margin-right: 0.5em; + margin-top: 0.5em; display: flex; align-items: center; justify-content: center; - + box-sizing: border-box; :first-child { margin-right: 0.25em; } + :last-child { + width: 1.5em; + } + &:focus { + outline: none; + } +} + +.picked-reaction { + border: 1px solid var(--link, $fallback--link); + margin-left: -1px; // offset the border, can't use inset shadows either + margin-right: calc(0.5em - 1px); } .button-icon.icon-reply { diff --git a/src/modules/statuses.js b/src/modules/statuses.js index c285b452..fcb6d1f3 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -1,4 +1,20 @@ -import { remove, slice, each, findIndex, find, maxBy, minBy, merge, first, last, isArray, omitBy, findKey } from 'lodash' +import { + remove, + slice, + each, + findIndex, + find, + maxBy, + minBy, + merge, + first, + last, + isArray, + omitBy, + flow, + filter, + keys +} from 'lodash' import { set } from 'vue' import apiService from '../services/api/api.service.js' // import parse from '../services/status_parser/status_parser.js' @@ -512,8 +528,29 @@ export const mutations = { }, addEmojiReactions (state, { id, emojiReactions, currentUser }) { const status = state.allStatusesObject[id] - status.emojiReactions = emojiReactions - status.reactedWithEmoji = findKey(emojiReactions, { id: currentUser.id }) + set(status, 'emojiReactions', emojiReactions) + const reactedWithEmoji = flow(keys, filter(reaction => find(reaction, { id: currentUser.id })))(emojiReactions) + set(status, 'reactedWithEmoji', reactedWithEmoji) + }, + addOwnReaction (state, { id, emoji, currentUser }) { + const status = state.allStatusesObject[id] + status.emojiReactions = status.emojiReactions || {} + const listOfUsers = (status.emojiReactions && status.emojiReactions[emoji]) || [] + const hasSelfAlready = !!find(listOfUsers, { id: currentUser.id }) + if (!hasSelfAlready) { + set(status.emojiReactions, emoji, listOfUsers.concat([{ id: currentUser.id }])) + set(status, 'reactedWithEmoji', emoji) + } + }, + removeOwnReaction (state, { id, emoji, currentUser }) { + const status = state.allStatusesObject[id] + const listOfUsers = status.emojiReactions[emoji] || [] + const hasSelfAlready = !!find(listOfUsers, { id: currentUser.id }) + if (hasSelfAlready) { + const newUsers = filter(listOfUsers, user => user.id !== currentUser.id) + set(status.emojiReactions, emoji, newUsers) + set(status, 'reactedWith', emoji) + } }, updateStatusWithPoll (state, { id, poll }) { const status = state.allStatusesObject[id] @@ -616,6 +653,24 @@ const statuses = { commit('addRepeats', { id, rebloggedByUsers, currentUser: rootState.users.currentUser }) }) }, + reactWithEmoji ({ rootState, dispatch, commit }, { id, emoji }) { + const currentUser = rootState.users.currentUser + commit('addOwnReaction', { id, emoji, currentUser }) + rootState.api.backendInteractor.reactWithEmoji(id, emoji).then( + status => { + dispatch('fetchEmojiReactions', id) + } + ) + }, + unreactWithEmoji ({ rootState, dispatch, commit }, { id, emoji }) { + const currentUser = rootState.users.currentUser + commit('removeOwnReaction', { id, emoji, currentUser }) + rootState.api.backendInteractor.unreactWithEmoji(id, emoji).then( + status => { + dispatch('fetchEmojiReactions', id) + } + ) + }, fetchEmojiReactions ({ rootState, commit }, id) { rootState.api.backendInteractor.fetchEmojiReactions(id).then( emojiReactions => { diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 7ef4b74a..2e96264a 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -72,6 +72,8 @@ const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute` const MASTODON_SEARCH_2 = `/api/v2/search` const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search' const PLEROMA_EMOJI_REACTIONS_URL = id => `/api/v1/pleroma/statuses/${id}/emoji_reactions_by` +const PLEROMA_EMOJI_REACT_URL = id => `/api/v1/pleroma/statuses/${id}/react_with_emoji` +const PLEROMA_EMOJI_UNREACT_URL = id => `/api/v1/pleroma/statuses/${id}/unreact_with_emoji` const oldfetch = window.fetch @@ -869,6 +871,24 @@ const fetchEmojiReactions = ({ id }) => { return promisedRequest({ url: PLEROMA_EMOJI_REACTIONS_URL(id) }) } +const reactWithEmoji = ({ id, emoji, credentials }) => { + return promisedRequest({ + url: PLEROMA_EMOJI_REACT_URL(id), + method: 'POST', + credentials, + payload: { emoji } + }).then(status => parseStatus(status)) +} + +const unreactWithEmoji = ({ id, emoji, credentials }) => { + return promisedRequest({ + url: PLEROMA_EMOJI_UNREACT_URL(id), + method: 'POST', + credentials, + payload: { emoji } + }).then(parseStatus) +} + const reportUser = ({ credentials, userId, statusIds, comment, forward }) => { return promisedRequest({ url: MASTODON_REPORT_USER_URL, @@ -1003,6 +1023,8 @@ const apiService = { fetchFavoritedByUsers, fetchRebloggedByUsers, fetchEmojiReactions, + reactWithEmoji, + unreactWithEmoji, reportUser, updateNotificationSettings, search2, diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js index 52234fcc..44233a24 100644 --- a/src/services/backend_interactor_service/backend_interactor_service.js +++ b/src/services/backend_interactor_service/backend_interactor_service.js @@ -144,6 +144,8 @@ const backendInteractorService = credentials => { const fetchFavoritedByUsers = (id) => apiService.fetchFavoritedByUsers({ id }) const fetchRebloggedByUsers = (id) => apiService.fetchRebloggedByUsers({ id }) const fetchEmojiReactions = (id) => apiService.fetchEmojiReactions({ id }) + const reactWithEmoji = (id, emoji) => apiService.reactWithEmoji({ id, emoji, credentials }) + const unreactWithEmoji = (id, emoji) => apiService.unreactWithEmoji({ id, emoji, credentials }) const reportUser = (params) => apiService.reportUser({ credentials, ...params }) const favorite = (id) => apiService.favorite({ id, credentials }) @@ -212,6 +214,8 @@ const backendInteractorService = credentials => { fetchFavoritedByUsers, fetchRebloggedByUsers, fetchEmojiReactions, + reactWithEmoji, + unreactWithEmoji, reportUser, favorite, unfavorite, -- cgit v1.2.3-70-g09d2 From 7a013ac39392ef251c0789f27dd4660dcd30bd6d Mon Sep 17 00:00:00 2001 From: Shpuld Shpludson Date: Wed, 15 Jan 2020 20:22:54 +0000 Subject: Implement domain mutes v2 --- .../domain_mute_card/domain_mute_card.js | 15 ++ .../domain_mute_card/domain_mute_card.vue | 38 +++++ src/components/user_settings/user_settings.js | 19 ++- src/components/user_settings/user_settings.vue | 157 +++++++++++++++------ src/i18n/en.json | 9 ++ src/modules/users.js | 44 ++++++ src/services/api/api.service.js | 28 +++- 7 files changed, 263 insertions(+), 47 deletions(-) create mode 100644 src/components/domain_mute_card/domain_mute_card.js create mode 100644 src/components/domain_mute_card/domain_mute_card.vue (limited to 'src/services/api/api.service.js') diff --git a/src/components/domain_mute_card/domain_mute_card.js b/src/components/domain_mute_card/domain_mute_card.js new file mode 100644 index 00000000..c8e838ba --- /dev/null +++ b/src/components/domain_mute_card/domain_mute_card.js @@ -0,0 +1,15 @@ +import ProgressButton from '../progress_button/progress_button.vue' + +const DomainMuteCard = { + props: ['domain'], + components: { + ProgressButton + }, + methods: { + unmuteDomain () { + return this.$store.dispatch('unmuteDomain', this.domain) + } + } +} + +export default DomainMuteCard diff --git a/src/components/domain_mute_card/domain_mute_card.vue b/src/components/domain_mute_card/domain_mute_card.vue new file mode 100644 index 00000000..567d81c5 --- /dev/null +++ b/src/components/domain_mute_card/domain_mute_card.vue @@ -0,0 +1,38 @@ + + + + + diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js index d5d671e4..1709b48f 100644 --- a/src/components/user_settings/user_settings.js +++ b/src/components/user_settings/user_settings.js @@ -9,6 +9,7 @@ import ScopeSelector from '../scope_selector/scope_selector.vue' import fileSizeFormatService from '../../services/file_size_format/file_size_format.js' import BlockCard from '../block_card/block_card.vue' import MuteCard from '../mute_card/mute_card.vue' +import DomainMuteCard from '../domain_mute_card/domain_mute_card.vue' import SelectableList from '../selectable_list/selectable_list.vue' import ProgressButton from '../progress_button/progress_button.vue' import EmojiInput from '../emoji_input/emoji_input.vue' @@ -32,6 +33,12 @@ const MuteList = withSubscription({ childPropName: 'items' })(SelectableList) +const DomainMuteList = withSubscription({ + fetch: (props, $store) => $store.dispatch('fetchDomainMutes'), + select: (props, $store) => get($store.state.users.currentUser, 'domainMutes', []), + childPropName: 'items' +})(SelectableList) + const UserSettings = { data () { return { @@ -67,7 +74,8 @@ const UserSettings = { changedPassword: false, changePasswordError: false, activeTab: 'profile', - notificationSettings: this.$store.state.users.currentUser.notification_settings + notificationSettings: this.$store.state.users.currentUser.notification_settings, + newDomainToMute: '' } }, created () { @@ -80,10 +88,12 @@ const UserSettings = { ImageCropper, BlockList, MuteList, + DomainMuteList, EmojiInput, Autosuggest, BlockCard, MuteCard, + DomainMuteCard, ProgressButton, Importer, Exporter, @@ -365,6 +375,13 @@ const UserSettings = { unmuteUsers (ids) { return this.$store.dispatch('unmuteUsers', ids) }, + unmuteDomains (domains) { + return this.$store.dispatch('unmuteDomains', domains) + }, + muteDomain () { + return this.$store.dispatch('muteDomain', this.newDomainToMute) + .then(() => { this.newDomainToMute = '' }) + }, identity (value) { return value } diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue index 3f1982a6..2222c293 100644 --- a/src/components/user_settings/user_settings.vue +++ b/src/components/user_settings/user_settings.vue @@ -509,59 +509,114 @@
-
- - - -
- - - - - + + + + + +
+ @@ -639,6 +694,18 @@ } } + &-domain-mute-form { + padding: 1em; + display: flex; + flex-direction: column; + + button { + align-self: flex-end; + margin-top: 1em; + width: 10em; + } + } + .setting-subitem { margin-left: 1.75em; } diff --git a/src/i18n/en.json b/src/i18n/en.json index 75d66b9f..31f4ac24 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -21,6 +21,12 @@ "chat": { "title": "Chat" }, + "domain_mute_card": { + "mute": "Mute", + "mute_progress": "Muting...", + "unmute": "Unmute", + "unmute_progress": "Unmuting..." + }, "exporter": { "export": "Export", "processing": "Processing, you'll soon be asked to download your file" @@ -264,6 +270,7 @@ "delete_account_error": "There was an issue deleting your account. If this persists please contact your instance administrator.", "delete_account_instructions": "Type your password in the input below to confirm account deletion.", "discoverable": "Allow discovery of this account in search results and other services", + "domain_mutes": "Domains", "avatar_size_instruction": "The recommended minimum size for avatar images is 150x150 pixels.", "pad_emoji": "Pad emoji with spaces when adding from picker", "export_theme": "Save preset", @@ -361,6 +368,7 @@ "post_status_content_type": "Post status content type", "stop_gifs": "Play-on-hover GIFs", "streaming": "Enable automatic streaming of new posts when scrolled to the top", + "user_mutes": "Users", "useStreamingApi": "Receive posts and notifications real-time", "useStreamingApiWarning": "(Not recommended, experimental, known to skip posts)", "text": "Text", @@ -369,6 +377,7 @@ "theme_help_v2_1": "You can also override certain component's colors and opacity by toggling the checkbox, use \"Clear all\" button to clear all overrides.", "theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.", "tooltipRadius": "Tooltips/alerts", + "type_domains_to_mute": "Type in domains to mute", "upload_a_photo": "Upload a photo", "user_settings": "User Settings", "values": { diff --git a/src/modules/users.js b/src/modules/users.js index b9ed0efa..ce3e595d 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -72,6 +72,16 @@ const showReblogs = (store, userId) => { .then((relationship) => store.commit('updateUserRelationship', [relationship])) } +const muteDomain = (store, domain) => { + return store.rootState.api.backendInteractor.muteDomain({ domain }) + .then(() => store.commit('addDomainMute', domain)) +} + +const unmuteDomain = (store, domain) => { + return store.rootState.api.backendInteractor.unmuteDomain({ domain }) + .then(() => store.commit('removeDomainMute', domain)) +} + export const mutations = { setMuted (state, { user: { id }, muted }) { const user = state.usersObject[id] @@ -177,6 +187,20 @@ export const mutations = { state.currentUser.muteIds.push(muteId) } }, + saveDomainMutes (state, domainMutes) { + state.currentUser.domainMutes = domainMutes + }, + addDomainMute (state, domain) { + if (state.currentUser.domainMutes.indexOf(domain) === -1) { + state.currentUser.domainMutes.push(domain) + } + }, + removeDomainMute (state, domain) { + const index = state.currentUser.domainMutes.indexOf(domain) + if (index !== -1) { + state.currentUser.domainMutes.splice(index, 1) + } + }, setPinnedToUser (state, status) { const user = state.usersObject[status.user.id] const index = user.pinnedStatusIds.indexOf(status.id) @@ -297,6 +321,25 @@ const users = { unmuteUsers (store, ids = []) { return Promise.all(ids.map(id => unmuteUser(store, id))) }, + fetchDomainMutes (store) { + return store.rootState.api.backendInteractor.fetchDomainMutes() + .then((domainMutes) => { + store.commit('saveDomainMutes', domainMutes) + return domainMutes + }) + }, + muteDomain (store, domain) { + return muteDomain(store, domain) + }, + unmuteDomain (store, domain) { + return unmuteDomain(store, domain) + }, + muteDomains (store, domains = []) { + return Promise.all(domains.map(domain => muteDomain(store, domain))) + }, + unmuteDomains (store, domain = []) { + return Promise.all(domain.map(domain => unmuteDomain(store, domain))) + }, fetchFriends ({ rootState, commit }, id) { const user = rootState.users.usersObject[id] const maxId = last(user.friendIds) @@ -460,6 +503,7 @@ const users = { user.credentials = accessToken user.blockIds = [] user.muteIds = [] + user.domainMutes = [] commit('setCurrentUser', user) commit('addNewUsers', [user]) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index ef0267aa..dcbedd8b 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -72,6 +72,7 @@ const MASTODON_MUTE_CONVERSATION = id => `/api/v1/statuses/${id}/mute` const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute` const MASTODON_SEARCH_2 = `/api/v2/search` const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search' +const MASTODON_DOMAIN_BLOCKS_URL = '/api/v1/domain_blocks' const MASTODON_STREAMING = '/api/v1/streaming' const oldfetch = window.fetch @@ -948,6 +949,28 @@ const search2 = ({ credentials, q, resolve, limit, offset, following }) => { }) } +const fetchDomainMutes = ({ credentials }) => { + return promisedRequest({ url: MASTODON_DOMAIN_BLOCKS_URL, credentials }) +} + +const muteDomain = ({ domain, credentials }) => { + return promisedRequest({ + url: MASTODON_DOMAIN_BLOCKS_URL, + method: 'POST', + payload: { domain }, + credentials + }) +} + +const unmuteDomain = ({ domain, credentials }) => { + return promisedRequest({ + url: MASTODON_DOMAIN_BLOCKS_URL, + method: 'DELETE', + payload: { domain }, + credentials + }) +} + export const getMastodonSocketURI = ({ credentials, stream, args = {} }) => { return Object.entries({ ...(credentials @@ -1110,7 +1133,10 @@ const apiService = { reportUser, updateNotificationSettings, search2, - searchUsers + searchUsers, + fetchDomainMutes, + muteDomain, + unmuteDomain } export default apiService -- cgit v1.2.3-70-g09d2 From e6291e4ee179ab85f212b1eef7d9e03565e6a8f8 Mon Sep 17 00:00:00 2001 From: Shpuld Shpuldson Date: Mon, 27 Jan 2020 18:43:26 +0200 Subject: remove unnecessary anonymous function --- src/services/api/api.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/services/api/api.service.js') diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index aa31f123..11aa0675 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -894,7 +894,7 @@ const reactWithEmoji = ({ id, emoji, credentials }) => { method: 'POST', credentials, payload: { emoji } - }).then(status => parseStatus(status)) + }).then(parseStatus) } const unreactWithEmoji = ({ id, emoji, credentials }) => { -- cgit v1.2.3-70-g09d2 From a06f3a7fbc40cd51df3c2d04406494cf60b0cf8a Mon Sep 17 00:00:00 2001 From: kPherox Date: Tue, 4 Feb 2020 04:37:29 +0900 Subject: Add `with_move` param for fetching notification --- src/services/api/api.service.js | 6 +++++- src/services/notifications_fetcher/notifications_fetcher.service.js | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) (limited to 'src/services/api/api.service.js') diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 11aa0675..b794fd58 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -495,7 +495,8 @@ const fetchTimeline = ({ until = false, userId = false, tag = false, - withMuted = false + withMuted = false, + withMove = false }) => { const timelineUrls = { public: MASTODON_PUBLIC_TIMELINE, @@ -535,6 +536,9 @@ const fetchTimeline = ({ if (timeline === 'public' || timeline === 'publicAndExternal') { params.push(['only_media', false]) } + if (timeline === 'notifications') { + params.push(['with_move', withMove]) + } params.push(['count', 20]) params.push(['with_muted', withMuted]) diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js index 64499a1b..864e32f8 100644 --- a/src/services/notifications_fetcher/notifications_fetcher.service.js +++ b/src/services/notifications_fetcher/notifications_fetcher.service.js @@ -11,9 +11,12 @@ const fetchAndUpdate = ({ store, credentials, older = false }) => { const rootState = store.rootState || store.state const timelineData = rootState.statuses.notifications const hideMutedPosts = getters.mergedConfig.hideMutedPosts + const allowFollowingMove = rootState.users.currentUser.allow_following_move args['withMuted'] = !hideMutedPosts + args['withMove'] = !allowFollowingMove + args['timeline'] = 'notifications' if (older) { if (timelineData.minId !== Number.POSITIVE_INFINITY) { -- cgit v1.2.3-70-g09d2 From f6b482be515ea4f0281050f71296ffe0ec2ab305 Mon Sep 17 00:00:00 2001 From: Shpuld Shpludson Date: Tue, 11 Feb 2020 12:24:51 +0000 Subject: Emoji Reactions - fixes and improvements --- src/App.scss | 2 +- src/_variables.scss | 2 + src/components/emoji_reactions/emoji_reactions.js | 48 +++++++++- src/components/emoji_reactions/emoji_reactions.vue | 103 +++++++++++++++++++-- src/components/notification/notification.vue | 7 ++ src/components/notifications/notifications.scss | 4 + src/components/react_button/react_button.js | 7 +- src/components/react_button/react_button.vue | 4 + src/components/settings/settings.vue | 10 ++ src/components/status/status.vue | 1 + src/i18n/en.json | 5 +- src/i18n/fi.json | 5 +- src/modules/config.js | 4 +- src/modules/statuses.js | 36 ++++--- src/services/api/api.service.js | 28 +++--- .../entity_normalizer/entity_normalizer.service.js | 1 + .../notification_utils/notification_utils.js | 3 +- test/unit/specs/modules/statuses.spec.js | 13 ++- 18 files changed, 236 insertions(+), 47 deletions(-) (limited to 'src/services/api/api.service.js') diff --git a/src/App.scss b/src/App.scss index 754ca62e..922e39b6 100644 --- a/src/App.scss +++ b/src/App.scss @@ -75,7 +75,7 @@ button { border-radius: $fallback--btnRadius; border-radius: var(--btnRadius, $fallback--btnRadius); cursor: pointer; - box-shadow: 0px 0px 2px 0px rgba(0, 0, 0, 1), 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset; + box-shadow: $fallback--buttonShadow; box-shadow: var(--buttonShadow); font-size: 14px; font-family: sans-serif; diff --git a/src/_variables.scss b/src/_variables.scss index e18101f0..30dc3e42 100644 --- a/src/_variables.scss +++ b/src/_variables.scss @@ -27,3 +27,5 @@ $fallback--tooltipRadius: 5px; $fallback--avatarRadius: 4px; $fallback--avatarAltRadius: 10px; $fallback--attachmentRadius: 10px; + +$fallback--buttonShadow: 0px 0px 2px 0px rgba(0, 0, 0, 1), 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset; diff --git a/src/components/emoji_reactions/emoji_reactions.js b/src/components/emoji_reactions/emoji_reactions.js index 95d52cb6..b799ac9a 100644 --- a/src/components/emoji_reactions/emoji_reactions.js +++ b/src/components/emoji_reactions/emoji_reactions.js @@ -1,17 +1,55 @@ +import UserAvatar from '../user_avatar/user_avatar.vue' + +const EMOJI_REACTION_COUNT_CUTOFF = 12 const EmojiReactions = { name: 'EmojiReactions', + components: { + UserAvatar + }, props: ['status'], + data: () => ({ + showAll: false, + popperOptions: { + modifiers: { + preventOverflow: { padding: { top: 50 }, boundariesElement: 'viewport' } + } + } + }), computed: { + tooManyReactions () { + return this.status.emoji_reactions.length > EMOJI_REACTION_COUNT_CUTOFF + }, emojiReactions () { - return this.status.emoji_reactions + return this.showAll + ? this.status.emoji_reactions + : this.status.emoji_reactions.slice(0, EMOJI_REACTION_COUNT_CUTOFF) + }, + showMoreString () { + return `+${this.status.emoji_reactions.length - EMOJI_REACTION_COUNT_CUTOFF}` + }, + accountsForEmoji () { + return this.status.emoji_reactions.reduce((acc, reaction) => { + acc[reaction.name] = reaction.accounts || [] + return acc + }, {}) + }, + loggedIn () { + return !!this.$store.state.users.currentUser } }, methods: { + toggleShowAll () { + this.showAll = !this.showAll + }, reactedWith (emoji) { - const user = this.$store.state.users.currentUser - const reaction = this.status.emoji_reactions.find(r => r.emoji === emoji) - return reaction.accounts && reaction.accounts.find(u => u.id === user.id) + return this.status.emoji_reactions.find(r => r.name === emoji).me + }, + fetchEmojiReactionsByIfMissing () { + const hasNoAccounts = this.status.emoji_reactions.find(r => !r.accounts) + if (hasNoAccounts) { + this.$store.dispatch('fetchEmojiReactionsBy', this.status.id) + } }, reactWith (emoji) { this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji }) @@ -20,6 +58,8 @@ const EmojiReactions = { this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji }) }, emojiOnClick (emoji, event) { + if (!this.loggedIn) return + if (this.reactedWith(emoji)) { this.unreact(emoji) } else { diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue index 00d6d2b7..e5b6d9f5 100644 --- a/src/components/emoji_reactions/emoji_reactions.vue +++ b/src/components/emoji_reactions/emoji_reactions.vue @@ -1,16 +1,58 @@ @@ -23,6 +65,31 @@ flex-wrap: wrap; } +.reacted-users { + padding: 0.5em; +} + +.reacted-user { + padding: 0.25em; + display: flex; + flex-direction: row; + + .reacted-user-names { + display: flex; + flex-direction: column; + margin-left: 0.5em; + + img { + width: 1em; + height: 1em; + } + } + + .reacted-user-screen-name { + font-size: 9px; + } +} + .emoji-reaction { padding: 0 0.5em; margin-right: 0.5em; @@ -38,6 +105,26 @@ &:focus { outline: none; } + + &.not-clickable { + cursor: default; + &:hover { + box-shadow: $fallback--buttonShadow; + box-shadow: var(--buttonShadow); + } + } +} + +.emoji-reaction-expand { + padding: 0 0.5em; + margin-right: 0.5em; + margin-top: 0.5em; + display: flex; + align-items: center; + justify-content: center; + &:hover { + text-decoration: underline; + } } .picked-reaction { diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue index 16124e50..411c0271 100644 --- a/src/components/notification/notification.vue +++ b/src/components/notification/notification.vue @@ -78,6 +78,13 @@ {{ $t('notifications.migrated_to') }} + + + + {{ notification.emoji }} + + +
r.name === emoji) + if (existingReaction && existingReaction.me) { + this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji }) + } else { + this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji }) + } this.closeReactionSelect() } }, diff --git a/src/components/react_button/react_button.vue b/src/components/react_button/react_button.vue index c925dd71..fb43ebaf 100644 --- a/src/components/react_button/react_button.vue +++ b/src/components/react_button/react_button.vue @@ -54,6 +54,10 @@ .reaction-picker-filter { padding: 0.5em; + display: flex; + input { + flex: 1; + } } .reaction-picker-divider { diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue index cef492f3..60cb8a87 100644 --- a/src/components/settings/settings.vue +++ b/src/components/settings/settings.vue @@ -92,6 +92,11 @@ {{ $t('settings.reply_link_preview') }} +
  • + + {{ $t('settings.emoji_reactions_on_timeline') }} + +
  • @@ -328,6 +333,11 @@ {{ $t('settings.notification_visibility_moves') }} +
  • + + {{ $t('settings.notification_visibility_emoji_reactions') }} + +
  • diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 0a82dcbe..83f07dac 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -369,6 +369,7 @@ diff --git a/src/i18n/en.json b/src/i18n/en.json index 74e71fc8..d0d654d3 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -126,7 +126,8 @@ "read": "Read!", "repeated_you": "repeated your status", "no_more_notifications": "No more notifications", - "migrated_to": "migrated to" + "migrated_to": "migrated to", + "reacted_with": "reacted with {0}" }, "polls": { "add_poll": "Add Poll", @@ -283,6 +284,7 @@ "domain_mutes": "Domains", "avatar_size_instruction": "The recommended minimum size for avatar images is 150x150 pixels.", "pad_emoji": "Pad emoji with spaces when adding from picker", + "emoji_reactions_on_timeline": "Show emoji reactions on timeline", "export_theme": "Save preset", "filtering": "Filtering", "filtering_explanation": "All statuses containing these words will be muted, one per line", @@ -331,6 +333,7 @@ "notification_visibility_mentions": "Mentions", "notification_visibility_repeats": "Repeats", "notification_visibility_moves": "User Migrates", + "notification_visibility_emoji_reactions": "Reactions", "no_rich_text_description": "Strip rich text formatting from all posts", "no_blocks": "No blocks", "no_mutes": "No mutes", diff --git a/src/i18n/fi.json b/src/i18n/fi.json index e7ed5408..ac8b2ac9 100644 --- a/src/i18n/fi.json +++ b/src/i18n/fi.json @@ -53,7 +53,8 @@ "notifications": "Ilmoitukset", "read": "Lue!", "repeated_you": "toisti viestisi", - "no_more_notifications": "Ei enempää ilmoituksia" + "no_more_notifications": "Ei enempää ilmoituksia", + "reacted_with": "lisäsi reaktion {0}" }, "polls": { "add_poll": "Lisää äänestys", @@ -140,6 +141,7 @@ "delete_account_description": "Poista tilisi ja viestisi pysyvästi.", "delete_account_error": "Virhe poistaessa tiliäsi. Jos virhe jatkuu, ota yhteyttä palvelimesi ylläpitoon.", "delete_account_instructions": "Syötä salasanasi vahvistaaksesi tilin poiston.", + "emoji_reactions_on_timeline": "Näytä emojireaktiot aikajanalla", "export_theme": "Tallenna teema", "filtering": "Suodatus", "filtering_explanation": "Kaikki viestit, jotka sisältävät näitä sanoja, suodatetaan. Yksi sana per rivi.", @@ -183,6 +185,7 @@ "notification_visibility_likes": "Tykkäykset", "notification_visibility_mentions": "Maininnat", "notification_visibility_repeats": "Toistot", + "notification_visibility_emoji_reactions": "Reaktiot", "no_rich_text_description": "Älä näytä tekstin muotoilua.", "hide_network_description": "Älä näytä seurauksiani tai seuraajiani", "nsfw_clickthrough": "Piilota NSFW liitteet klikkauksen taakse", diff --git a/src/modules/config.js b/src/modules/config.js index de9f041b..8381fa53 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -20,6 +20,7 @@ export const defaultState = { autoLoad: true, streaming: false, hoverPreview: true, + emojiReactionsOnTimeline: true, autohideFloatingPostButton: false, pauseOnUnfocused: true, stopGifs: false, @@ -29,7 +30,8 @@ export const defaultState = { mentions: true, likes: true, repeats: true, - moves: true + moves: true, + emojiReactions: false }, webPushNotifications: false, muteWords: [], diff --git a/src/modules/statuses.js b/src/modules/statuses.js index ea0c1749..25b62ac7 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -81,7 +81,8 @@ const visibleNotificationTypes = (rootState) => { rootState.config.notificationVisibility.mentions && 'mention', rootState.config.notificationVisibility.repeats && 'repeat', rootState.config.notificationVisibility.follows && 'follow', - rootState.config.notificationVisibility.moves && 'move' + rootState.config.notificationVisibility.moves && 'move', + rootState.config.notificationVisibility.emojiReactions && 'pleroma:emoji_reactions' ].filter(_ => _) } @@ -325,6 +326,10 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot notification.status = notification.status && addStatusToGlobalStorage(state, notification.status).item } + if (notification.type === 'pleroma:emoji_reaction') { + dispatch('fetchEmojiReactionsBy', notification.status.id) + } + // Only add a new notification if we don't have one for the same action if (!state.notifications.idStore.hasOwnProperty(notification.id)) { state.notifications.maxId = notification.id > state.notifications.maxId @@ -358,7 +363,9 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot break } - if (i18nString) { + if (notification.type === 'pleroma:emoji_reaction') { + notifObj.body = rootGetters.i18n.t('notifications.reacted_with', [notification.emoji]) + } else if (i18nString) { notifObj.body = rootGetters.i18n.t('notifications.' + i18nString) } else { notifObj.body = notification.status.text @@ -371,10 +378,10 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot } if (!notification.seen && !state.notifications.desktopNotificationSilence && visibleNotificationTypes.includes(notification.type)) { - let notification = new window.Notification(title, notifObj) + let desktopNotification = new window.Notification(title, notifObj) // Chrome is known for not closing notifications automatically // according to MDN, anyway. - setTimeout(notification.close.bind(notification), 5000) + setTimeout(desktopNotification.close.bind(desktopNotification), 5000) } } } else if (notification.seen) { @@ -537,12 +544,13 @@ export const mutations = { }, addOwnReaction (state, { id, emoji, currentUser }) { const status = state.allStatusesObject[id] - const reactionIndex = findIndex(status.emoji_reactions, { emoji }) - const reaction = status.emoji_reactions[reactionIndex] || { emoji, count: 0, accounts: [] } + const reactionIndex = findIndex(status.emoji_reactions, { name: emoji }) + const reaction = status.emoji_reactions[reactionIndex] || { name: emoji, count: 0, accounts: [] } const newReaction = { ...reaction, count: reaction.count + 1, + me: true, accounts: [ ...reaction.accounts, currentUser @@ -558,21 +566,23 @@ export const mutations = { }, removeOwnReaction (state, { id, emoji, currentUser }) { const status = state.allStatusesObject[id] - const reactionIndex = findIndex(status.emoji_reactions, { emoji }) + const reactionIndex = findIndex(status.emoji_reactions, { name: emoji }) if (reactionIndex < 0) return const reaction = status.emoji_reactions[reactionIndex] + const accounts = reaction.accounts || [] const newReaction = { ...reaction, count: reaction.count - 1, - accounts: reaction.accounts.filter(acc => acc.id === currentUser.id) + me: false, + accounts: accounts.filter(acc => acc.id !== currentUser.id) } if (newReaction.count > 0) { set(status.emoji_reactions, reactionIndex, newReaction) } else { - set(status, 'emoji_reactions', status.emoji_reactions.filter(r => r.emoji !== emoji)) + set(status, 'emoji_reactions', status.emoji_reactions.filter(r => r.name !== emoji)) } }, updateStatusWithPoll (state, { id, poll }) { @@ -681,18 +691,22 @@ const statuses = { }, reactWithEmoji ({ rootState, dispatch, commit }, { id, emoji }) { const currentUser = rootState.users.currentUser + if (!currentUser) return + commit('addOwnReaction', { id, emoji, currentUser }) rootState.api.backendInteractor.reactWithEmoji({ id, emoji }).then( - status => { + ok => { dispatch('fetchEmojiReactionsBy', id) } ) }, unreactWithEmoji ({ rootState, dispatch, commit }, { id, emoji }) { const currentUser = rootState.users.currentUser + if (!currentUser) return + commit('removeOwnReaction', { id, emoji, currentUser }) rootState.api.backendInteractor.unreactWithEmoji({ id, emoji }).then( - status => { + ok => { dispatch('fetchEmojiReactionsBy', id) } ) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index b794fd58..20eaa9a0 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -74,9 +74,9 @@ const MASTODON_SEARCH_2 = `/api/v2/search` const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search' const MASTODON_DOMAIN_BLOCKS_URL = '/api/v1/domain_blocks' const MASTODON_STREAMING = '/api/v1/streaming' -const PLEROMA_EMOJI_REACTIONS_URL = id => `/api/v1/pleroma/statuses/${id}/emoji_reactions_by` -const PLEROMA_EMOJI_REACT_URL = id => `/api/v1/pleroma/statuses/${id}/react_with_emoji` -const PLEROMA_EMOJI_UNREACT_URL = id => `/api/v1/pleroma/statuses/${id}/unreact_with_emoji` +const PLEROMA_EMOJI_REACTIONS_URL = id => `/api/v1/pleroma/statuses/${id}/reactions` +const PLEROMA_EMOJI_REACT_URL = (id, emoji) => `/api/v1/pleroma/statuses/${id}/reactions/${emoji}` +const PLEROMA_EMOJI_UNREACT_URL = (id, emoji) => `/api/v1/pleroma/statuses/${id}/reactions/${emoji}` const oldfetch = window.fetch @@ -888,25 +888,27 @@ const fetchRebloggedByUsers = ({ id }) => { return promisedRequest({ url: MASTODON_STATUS_REBLOGGEDBY_URL(id) }).then((users) => users.map(parseUser)) } -const fetchEmojiReactions = ({ id }) => { - return promisedRequest({ url: PLEROMA_EMOJI_REACTIONS_URL(id) }) +const fetchEmojiReactions = ({ id, credentials }) => { + return promisedRequest({ url: PLEROMA_EMOJI_REACTIONS_URL(id), credentials }) + .then((reactions) => reactions.map(r => { + r.accounts = r.accounts.map(parseUser) + return r + })) } const reactWithEmoji = ({ id, emoji, credentials }) => { return promisedRequest({ - url: PLEROMA_EMOJI_REACT_URL(id), - method: 'POST', - credentials, - payload: { emoji } + url: PLEROMA_EMOJI_REACT_URL(id, emoji), + method: 'PUT', + credentials }).then(parseStatus) } const unreactWithEmoji = ({ id, emoji, credentials }) => { return promisedRequest({ - url: PLEROMA_EMOJI_UNREACT_URL(id), - method: 'POST', - credentials, - payload: { emoji } + url: PLEROMA_EMOJI_UNREACT_URL(id, emoji), + method: 'DELETE', + credentials }).then(parseStatus) } diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index 0a8abbbd..84169a7b 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -354,6 +354,7 @@ export const parseNotification = (data) => { ? null : parseUser(data.target) output.from_profile = parseUser(data.account) + output.emoji = data.emoji } else { const parsedNotice = parseStatus(data.notice) output.type = data.ntype diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js index 860620fc..b17bd7bf 100644 --- a/src/services/notification_utils/notification_utils.js +++ b/src/services/notification_utils/notification_utils.js @@ -7,7 +7,8 @@ export const visibleTypes = store => ([ store.state.config.notificationVisibility.mentions && 'mention', store.state.config.notificationVisibility.repeats && 'repeat', store.state.config.notificationVisibility.follows && 'follow', - store.state.config.notificationVisibility.moves && 'move' + store.state.config.notificationVisibility.moves && 'move', + store.state.config.notificationVisibility.emojiReactions && 'pleroma:emoji_reaction' ].filter(_ => _)) const sortById = (a, b) => { diff --git a/test/unit/specs/modules/statuses.spec.js b/test/unit/specs/modules/statuses.spec.js index e53aa388..fe42e85b 100644 --- a/test/unit/specs/modules/statuses.spec.js +++ b/test/unit/specs/modules/statuses.spec.js @@ -245,11 +245,12 @@ describe('Statuses module', () => { it('increments count in existing reaction', () => { const state = defaultState() const status = makeMockStatus({ id: '1' }) - status.emoji_reactions = [ { emoji: '😂', count: 1, accounts: [] } ] + status.emoji_reactions = [ { name: '😂', count: 1, accounts: [] } ] mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' }) mutations.addOwnReaction(state, { id: '1', emoji: '😂', currentUser: { id: 'me' } }) expect(state.allStatusesObject['1'].emoji_reactions[0].count).to.eql(2) + expect(state.allStatusesObject['1'].emoji_reactions[0].me).to.eql(true) expect(state.allStatusesObject['1'].emoji_reactions[0].accounts[0].id).to.eql('me') }) @@ -261,27 +262,29 @@ describe('Statuses module', () => { mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' }) mutations.addOwnReaction(state, { id: '1', emoji: '😂', currentUser: { id: 'me' } }) expect(state.allStatusesObject['1'].emoji_reactions[0].count).to.eql(1) + expect(state.allStatusesObject['1'].emoji_reactions[0].me).to.eql(true) expect(state.allStatusesObject['1'].emoji_reactions[0].accounts[0].id).to.eql('me') }) it('decreases count in existing reaction', () => { const state = defaultState() const status = makeMockStatus({ id: '1' }) - status.emoji_reactions = [ { emoji: '😂', count: 2, accounts: [{ id: 'me' }] } ] + status.emoji_reactions = [ { name: '😂', count: 2, accounts: [{ id: 'me' }] } ] mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' }) - mutations.removeOwnReaction(state, { id: '1', emoji: '😂', currentUser: {} }) + mutations.removeOwnReaction(state, { id: '1', emoji: '😂', currentUser: { id: 'me' } }) expect(state.allStatusesObject['1'].emoji_reactions[0].count).to.eql(1) + expect(state.allStatusesObject['1'].emoji_reactions[0].me).to.eql(false) expect(state.allStatusesObject['1'].emoji_reactions[0].accounts).to.eql([]) }) it('removes a reaction', () => { const state = defaultState() const status = makeMockStatus({ id: '1' }) - status.emoji_reactions = [{ emoji: '😂', count: 1, accounts: [{ id: 'me' }] }] + status.emoji_reactions = [{ name: '😂', count: 1, accounts: [{ id: 'me' }] }] mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' }) - mutations.removeOwnReaction(state, { id: '1', emoji: '😂', currentUser: {} }) + mutations.removeOwnReaction(state, { id: '1', emoji: '😂', currentUser: { id: 'me' } }) expect(state.allStatusesObject['1'].emoji_reactions.length).to.eql(0) }) }) -- cgit v1.2.3-70-g09d2 From 695d6b6c5070f935aeec5c4db1f21fb715803b1b Mon Sep 17 00:00:00 2001 From: eugenijm Date: Mon, 10 Feb 2020 23:53:56 +0300 Subject: Fix user activation/deactivation, deletion, and role assignment in the moderation menu --- src/modules/users.js | 4 ++-- src/services/api/api.service.js | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) (limited to 'src/services/api/api.service.js') diff --git a/src/modules/users.js b/src/modules/users.js index ce3e595d..df133be0 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -374,9 +374,9 @@ const users = { return rootState.api.backendInteractor.unsubscribeUser({ id }) .then((relationship) => commit('updateUserRelationship', [relationship])) }, - toggleActivationStatus ({ rootState, commit }, user) { + toggleActivationStatus ({ rootState, commit }, { user }) { const api = user.deactivated ? rootState.api.backendInteractor.activateUser : rootState.api.backendInteractor.deactivateUser - api(user) + api({ user }) .then(({ deactivated }) => commit('updateActivationStatus', { user, deactivated })) }, registerPushNotifications (store) { diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 20eaa9a0..03e88ae2 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -402,8 +402,8 @@ const fetchStatus = ({ id, credentials }) => { .then((data) => parseStatus(data)) } -const tagUser = ({ tag, credentials, ...options }) => { - const screenName = options.screen_name +const tagUser = ({ tag, credentials, user }) => { + const screenName = user.screen_name const form = { nicknames: [screenName], tags: [tag] @@ -419,8 +419,8 @@ const tagUser = ({ tag, credentials, ...options }) => { }) } -const untagUser = ({ tag, credentials, ...options }) => { - const screenName = options.screen_name +const untagUser = ({ tag, credentials, user }) => { + const screenName = user.screen_name const body = { nicknames: [screenName], tags: [tag] @@ -436,7 +436,7 @@ const untagUser = ({ tag, credentials, ...options }) => { }) } -const addRight = ({ right, credentials, ...user }) => { +const addRight = ({ right, credentials, user }) => { const screenName = user.screen_name return fetch(PERMISSION_GROUP_URL(screenName, right), { @@ -446,7 +446,7 @@ const addRight = ({ right, credentials, ...user }) => { }) } -const deleteRight = ({ right, credentials, ...user }) => { +const deleteRight = ({ right, credentials, user }) => { const screenName = user.screen_name return fetch(PERMISSION_GROUP_URL(screenName, right), { @@ -478,7 +478,7 @@ const deactivateUser = ({ credentials, user: { screen_name: nickname } }) => { }).then(response => get(response, 'users.0')) } -const deleteUser = ({ credentials, ...user }) => { +const deleteUser = ({ credentials, user }) => { const screenName = user.screen_name const headers = authHeaders(credentials) -- cgit v1.2.3-70-g09d2 From 40005240eb30ce59035299dc348a3962626e32c6 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 31 Mar 2020 14:46:38 -0500 Subject: Send credentials for favourited_by and reblogged_by API endpoints This ensures the data is fetchable on private instances --- src/services/api/api.service.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) (limited to 'src/services/api/api.service.js') diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 03e88ae2..7db1d094 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -880,12 +880,20 @@ const fetchPoll = ({ pollId, credentials }) => { ) } -const fetchFavoritedByUsers = ({ id }) => { - return promisedRequest({ url: MASTODON_STATUS_FAVORITEDBY_URL(id) }).then((users) => users.map(parseUser)) +const fetchFavoritedByUsers = ({ id, credentials }) => { + return promisedRequest({ + url: MASTODON_STATUS_FAVORITEDBY_URL(id), + method: 'GET', + credentials + }).then((users) => users.map(parseUser)) } -const fetchRebloggedByUsers = ({ id }) => { - return promisedRequest({ url: MASTODON_STATUS_REBLOGGEDBY_URL(id) }).then((users) => users.map(parseUser)) +const fetchRebloggedByUsers = ({ id, credentials }) => { + return promisedRequest({ + url: MASTODON_STATUS_REBLOGGEDBY_URL(id), + method: 'GET', + credentials + }).then((users) => users.map(parseUser)) } const fetchEmojiReactions = ({ id, credentials }) => { -- cgit v1.2.3-70-g09d2 From 18fa338d43b6b7b61b484ae6106ef3b95e5adeee Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 13 Apr 2020 15:26:55 +0400 Subject: Fix pagination --- src/services/api/api.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/services/api/api.service.js') diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 7db1d094..ad2b2ad5 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -540,7 +540,7 @@ const fetchTimeline = ({ params.push(['with_move', withMove]) } - params.push(['count', 20]) + params.push(['limit', 20]) params.push(['with_muted', withMuted]) const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&') -- cgit v1.2.3-70-g09d2 From 01b07f01e9340935faf51e5a3c8034cc90423989 Mon Sep 17 00:00:00 2001 From: eugenijm Date: Sat, 25 Apr 2020 07:04:39 +0300 Subject: Add support for follow request notifications --- CHANGELOG.md | 3 ++ src/components/notification/notification.js | 19 ++++++++++ src/components/notification/notification.vue | 44 ++++++++++++++++------ src/components/notifications/notifications.scss | 15 ++++++++ src/i18n/en.json | 5 ++- src/modules/config.js | 3 +- src/modules/statuses.js | 22 ++++++++++- src/services/api/api.service.js | 11 ++++++ .../entity_normalizer/entity_normalizer.service.js | 5 +-- .../notification_utils/notification_utils.js | 7 +++- static/fontello.json | 14 ++++++- 11 files changed, 128 insertions(+), 20 deletions(-) (limited to 'src/services/api/api.service.js') diff --git a/CHANGELOG.md b/CHANGELOG.md index f45561d0..685fe629 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Changed - Emoji autocomplete will match any part of the word and not just start, for example :drool will now helpfully suggest :blobcatdrool: and :blobcatdroolreach: +### Add +- Follow request notification support + ## [2.0.2] - 2020-04-08 ### Fixed - Favorite/Repeat avatars not showing up on private instances/non-public posts diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js index e7bd769e..6deee7d5 100644 --- a/src/components/notification/notification.js +++ b/src/components/notification/notification.js @@ -2,6 +2,7 @@ import Status from '../status/status.vue' import UserAvatar from '../user_avatar/user_avatar.vue' import UserCard from '../user_card/user_card.vue' import Timeago from '../timeago/timeago.vue' +import { isStatusNotification } from '../../services/notification_utils/notification_utils.js' import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' @@ -32,6 +33,21 @@ const Notification = { }, toggleMute () { this.unmuted = !this.unmuted + }, + approveUser () { + this.$store.state.api.backendInteractor.approveUser({ id: this.user.id }) + this.$store.dispatch('removeFollowRequest', this.user) + this.$store.dispatch('updateNotification', { + id: this.notification.id, + updater: notification => { + notification.type = 'follow' + } + }) + }, + denyUser () { + this.$store.state.api.backendInteractor.denyUser({ id: this.user.id }) + this.$store.dispatch('removeFollowRequest', this.user) + this.$store.dispatch('dismissNotification', { id: this.notification.id }) } }, computed: { @@ -57,6 +73,9 @@ const Notification = { }, needMute () { return this.user.muted + }, + isStatusNotification () { + return isStatusNotification(this.notification.type) } } } diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue index 51875747..02802776 100644 --- a/src/components/notification/notification.vue +++ b/src/components/notification/notification.vue @@ -74,6 +74,10 @@ {{ $t('notifications.followed_you') }} + + + {{ $t('notifications.follow_request') }} + {{ $t('notifications.migrated_to') }} @@ -87,30 +91,30 @@
    - + - +
    - + - +
    { each(notifications, (notification) => { - if (notification.type !== 'follow' && notification.type !== 'move') { + if (isStatusNotification(notification.type)) { notification.action = addStatusToGlobalStorage(state, notification.action).item notification.status = notification.status && addStatusToGlobalStorage(state, notification.status).item } @@ -361,13 +362,16 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot case 'move': i18nString = 'migrated_to' break + case 'follow_request': + i18nString = 'follow_request' + break } if (notification.type === 'pleroma:emoji_reaction') { notifObj.body = rootGetters.i18n.t('notifications.reacted_with', [notification.emoji]) } else if (i18nString) { notifObj.body = rootGetters.i18n.t('notifications.' + i18nString) - } else { + } else if (isStatusNotification(notification.type)) { notifObj.body = notification.status.text } @@ -521,6 +525,13 @@ export const mutations = { notification.seen = true }) }, + dismissNotification (state, { id }) { + state.notifications.data = state.notifications.data.filter(n => n.id !== id) + }, + updateNotification (state, { id, updater }) { + const notification = find(state.notifications.data, n => n.id === id) + notification && updater(notification) + }, queueFlush (state, { timeline, id }) { state.timelines[timeline].flushMarker = id }, @@ -680,6 +691,13 @@ const statuses = { credentials: rootState.users.currentUser.credentials }) }, + dismissNotification ({ rootState, commit }, { id }) { + rootState.api.backendInteractor.dismissNotification({ id }) + .then(() => commit('dismissNotification', { id })) + }, + updateNotification ({ rootState, commit }, { id, updater }) { + commit('updateNotification', { id, updater }) + }, fetchFavsAndRepeats ({ rootState, commit }, id) { Promise.all([ rootState.api.backendInteractor.fetchFavoritedByUsers({ id }), diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index ad2b2ad5..cda61ee2 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -29,6 +29,7 @@ const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials' const MASTODON_REGISTRATION_URL = '/api/v1/accounts' const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites' const MASTODON_USER_NOTIFICATIONS_URL = '/api/v1/notifications' +const MASTODON_DISMISS_NOTIFICATION_URL = id => `/api/v1/notifications/${id}/dismiss` const MASTODON_FAVORITE_URL = id => `/api/v1/statuses/${id}/favourite` const MASTODON_UNFAVORITE_URL = id => `/api/v1/statuses/${id}/unfavourite` const MASTODON_RETWEET_URL = id => `/api/v1/statuses/${id}/reblog` @@ -1010,6 +1011,15 @@ const unmuteDomain = ({ domain, credentials }) => { }) } +const dismissNotification = ({ credentials, id }) => { + return promisedRequest({ + url: MASTODON_DISMISS_NOTIFICATION_URL(id), + method: 'POST', + payload: { id }, + credentials + }) +} + export const getMastodonSocketURI = ({ credentials, stream, args = {} }) => { return Object.entries({ ...(credentials @@ -1165,6 +1175,7 @@ const apiService = { denyUser, suggestions, markNotificationsAsSeen, + dismissNotification, vote, fetchPoll, fetchFavoritedByUsers, diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index 84169a7b..6cacd0b8 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -1,4 +1,5 @@ import escape from 'escape-html' +import { isStatusNotification } from '../notification_utils/notification_utils.js' const qvitterStatusType = (status) => { if (status.is_post_verb) { @@ -346,9 +347,7 @@ export const parseNotification = (data) => { if (masto) { output.type = mastoDict[data.type] || data.type output.seen = data.pleroma.is_seen - output.status = output.type === 'follow' || output.type === 'move' - ? null - : parseStatus(data.status) + output.status = isStatusNotification(output.type) ? parseStatus(data.status) : null output.action = output.status // TODO: Refactor, this is unneeded output.target = output.type !== 'move' ? null diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js index b17bd7bf..eb479227 100644 --- a/src/services/notification_utils/notification_utils.js +++ b/src/services/notification_utils/notification_utils.js @@ -1,4 +1,4 @@ -import { filter, sortBy } from 'lodash' +import { filter, sortBy, includes } from 'lodash' export const notificationsFromStore = store => store.state.statuses.notifications.data @@ -7,10 +7,15 @@ export const visibleTypes = store => ([ store.state.config.notificationVisibility.mentions && 'mention', store.state.config.notificationVisibility.repeats && 'repeat', store.state.config.notificationVisibility.follows && 'follow', + store.state.config.notificationVisibility.followRequest && 'follow_request', store.state.config.notificationVisibility.moves && 'move', store.state.config.notificationVisibility.emojiReactions && 'pleroma:emoji_reaction' ].filter(_ => _)) +const statusNotifications = ['like', 'mention', 'repeat', 'pleroma:emoji_reaction'] + +export const isStatusNotification = (type) => includes(statusNotifications, type) + const sortById = (a, b) => { const seqA = Number(a.id) const seqB = Number(b.id) diff --git a/static/fontello.json b/static/fontello.json index 5a7086a2..5963b68b 100755 --- a/static/fontello.json +++ b/static/fontello.json @@ -345,6 +345,18 @@ "css": "link", "code": 59427, "src": "fontawesome" + }, + { + "uid": "8b80d36d4ef43889db10bc1f0dc9a862", + "css": "user", + "code": 59428, + "src": "fontawesome" + }, + { + "uid": "12f4ece88e46abd864e40b35e05b11cd", + "css": "ok", + "code": 59431, + "src": "fontawesome" } ] -} +} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 02c8a9e3143f2b12f44d24f307e2718dec22987b Mon Sep 17 00:00:00 2001 From: Shpuld Shpuldson Date: Fri, 1 May 2020 17:26:07 +0300 Subject: remove with_move param --- src/services/api/api.service.js | 6 +----- src/services/notifications_fetcher/notifications_fetcher.service.js | 3 --- 2 files changed, 1 insertion(+), 8 deletions(-) (limited to 'src/services/api/api.service.js') diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index ad2b2ad5..3c6b8f4e 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -495,8 +495,7 @@ const fetchTimeline = ({ until = false, userId = false, tag = false, - withMuted = false, - withMove = false + withMuted = false }) => { const timelineUrls = { public: MASTODON_PUBLIC_TIMELINE, @@ -536,9 +535,6 @@ const fetchTimeline = ({ if (timeline === 'public' || timeline === 'publicAndExternal') { params.push(['only_media', false]) } - if (timeline === 'notifications') { - params.push(['with_move', withMove]) - } params.push(['limit', 20]) params.push(['with_muted', withMuted]) diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js index 864e32f8..64499a1b 100644 --- a/src/services/notifications_fetcher/notifications_fetcher.service.js +++ b/src/services/notifications_fetcher/notifications_fetcher.service.js @@ -11,12 +11,9 @@ const fetchAndUpdate = ({ store, credentials, older = false }) => { const rootState = store.rootState || store.state const timelineData = rootState.statuses.notifications const hideMutedPosts = getters.mergedConfig.hideMutedPosts - const allowFollowingMove = rootState.users.currentUser.allow_following_move args['withMuted'] = !hideMutedPosts - args['withMove'] = !allowFollowingMove - args['timeline'] = 'notifications' if (older) { if (timelineData.minId !== Number.POSITIVE_INFINITY) { -- cgit v1.2.3-70-g09d2 From 75519223f9a715aacb99d3780ee681089a479292 Mon Sep 17 00:00:00 2001 From: Shpuld Shpuldson Date: Sat, 2 May 2020 10:52:57 +0300 Subject: mark single notifs as seen properly on server --- src/components/follow_request_card/follow_request_card.js | 2 +- src/components/notification/notification.js | 2 +- src/modules/statuses.js | 12 ++++++++++++ src/services/api/api.service.js | 12 ++++++++---- 4 files changed, 22 insertions(+), 6 deletions(-) (limited to 'src/services/api/api.service.js') diff --git a/src/components/follow_request_card/follow_request_card.js b/src/components/follow_request_card/follow_request_card.js index 2a9d3db5..33e2699e 100644 --- a/src/components/follow_request_card/follow_request_card.js +++ b/src/components/follow_request_card/follow_request_card.js @@ -18,11 +18,11 @@ const FollowRequestCard = { this.$store.dispatch('removeFollowRequest', this.user) const notifId = this.findFollowRequestNotificationId() + this.$store.dispatch('markSingleNotificationAsSeen', { id: notifId }) this.$store.dispatch('updateNotification', { id: notifId, updater: notification => { notification.type = 'follow' - notification.seen = true } }) }, diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js index 8c20ff09..abe3bebe 100644 --- a/src/components/notification/notification.js +++ b/src/components/notification/notification.js @@ -37,11 +37,11 @@ const Notification = { approveUser () { this.$store.state.api.backendInteractor.approveUser({ id: this.user.id }) this.$store.dispatch('removeFollowRequest', this.user) + this.$store.dispatch('markSingleNotificationAsSeen', { id: this.notification.id }) this.$store.dispatch('updateNotification', { id: this.notification.id, updater: notification => { notification.type = 'follow' - notification.seen = true } }) }, diff --git a/src/modules/statuses.js b/src/modules/statuses.js index 239f41eb..2a8b9581 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -525,6 +525,10 @@ export const mutations = { notification.seen = true }) }, + markSingleNotificationAsSeen (state, { id }) { + const notification = find(state.notifications.data, n => n.id === id) + if (notification) notification.seen = true + }, dismissNotification (state, { id }) { state.notifications.data = state.notifications.data.filter(n => n.id !== id) }, @@ -691,6 +695,14 @@ const statuses = { credentials: rootState.users.currentUser.credentials }) }, + markSingleNotificationAsSeen ({ rootState, commit }, { id }) { + commit('markSingleNotificationAsSeen', { id }) + apiService.markNotificationsAsSeen({ + single: true, + id, + credentials: rootState.users.currentUser.credentials + }) + }, dismissNotification ({ rootState, commit }, { id }) { rootState.api.backendInteractor.dismissNotification({ id }) .then(() => commit('dismissNotification', { id })) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 3a58c38d..72c8874f 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -4,7 +4,6 @@ import 'whatwg-fetch' import { RegistrationError, StatusCodeError } from '../errors/errors' /* eslint-env browser */ -const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json' const BLOCKS_IMPORT_URL = '/api/pleroma/blocks_import' const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import' const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account' @@ -17,6 +16,7 @@ const DEACTIVATE_USER_URL = '/api/pleroma/admin/users/deactivate' const ADMIN_USERS_URL = '/api/pleroma/admin/users' const SUGGESTIONS_URL = '/api/v1/suggestions' const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings' +const NOTIFICATION_READ_URL = '/api/v1/pleroma/notifications/read' const MFA_SETTINGS_URL = '/api/pleroma/accounts/mfa' const MFA_BACKUP_CODES_URL = '/api/pleroma/accounts/mfa/backup_codes' @@ -841,12 +841,16 @@ const suggestions = ({ credentials }) => { }).then((data) => data.json()) } -const markNotificationsAsSeen = ({ id, credentials }) => { +const markNotificationsAsSeen = ({ id, credentials, single = false }) => { const body = new FormData() - body.append('latest_id', id) + if (single) { + body.append('id', id) + } else { + body.append('max_id', id) + } - return fetch(QVITTER_USER_NOTIFICATIONS_READ_URL, { + return fetch(NOTIFICATION_READ_URL, { body, headers: authHeaders(credentials), method: 'POST' -- cgit v1.2.3-70-g09d2 From 8e399710988dd874bd921a9ced76daf90034c29f Mon Sep 17 00:00:00 2001 From: Shpuld Shpuldson Date: Wed, 13 May 2020 17:48:31 +0300 Subject: add with_relationships where necessary --- src/components/post_status_form/post_status_form.js | 2 +- src/components/user_settings/user_settings.js | 4 ++-- src/modules/users.js | 4 ++-- src/services/api/api.service.js | 13 +++++++++---- 4 files changed, 14 insertions(+), 9 deletions(-) (limited to 'src/services/api/api.service.js') diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 74067fef..a98e1e31 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -102,7 +102,7 @@ const PostStatusForm = { ...this.$store.state.instance.customEmoji ], users: this.$store.state.users.users, - updateUsersList: (input) => this.$store.dispatch('searchUsers', input) + updateUsersList: (query) => this.$store.dispatch('searchUsers', { query }) }) }, emojiSuggestor () { diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js index 5338c974..a1ec2997 100644 --- a/src/components/user_settings/user_settings.js +++ b/src/components/user_settings/user_settings.js @@ -112,7 +112,7 @@ const UserSettings = { ...this.$store.state.instance.customEmoji ], users: this.$store.state.users.users, - updateUsersList: (input) => this.$store.dispatch('searchUsers', input) + updateUsersList: (query) => this.$store.dispatch('searchUsers', { query }) }) }, emojiSuggestor () { @@ -362,7 +362,7 @@ const UserSettings = { }) }, queryUserIds (query) { - return this.$store.dispatch('searchUsers', query) + return this.$store.dispatch('searchUsers', { query }) .then((users) => map(users, 'id')) }, blockUsers (ids) { diff --git a/src/modules/users.js b/src/modules/users.js index 1d1b415c..f377da75 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -428,8 +428,8 @@ const users = { store.commit('setUserForNotification', notification) }) }, - searchUsers (store, query) { - return store.rootState.api.backendInteractor.searchUsers({ query }) + searchUsers (store, { query, withRelationships }) { + return store.rootState.api.backendInteractor.searchUsers({ query, withRelationships }) .then((users) => { store.commit('addNewUsers', users) return users diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 72c8874f..94ff4623 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -324,7 +324,8 @@ const fetchFriends = ({ id, maxId, sinceId, limit = 20, credentials }) => { const args = [ maxId && `max_id=${maxId}`, sinceId && `since_id=${sinceId}`, - limit && `limit=${limit}` + limit && `limit=${limit}`, + `with_relationships=true` ].filter(_ => _).join('&') url = url + (args ? '?' + args : '') @@ -358,7 +359,8 @@ const fetchFollowers = ({ id, maxId, sinceId, limit = 20, credentials }) => { const args = [ maxId && `max_id=${maxId}`, sinceId && `since_id=${sinceId}`, - limit && `limit=${limit}` + limit && `limit=${limit}`, + `with_relationships=true` ].filter(_ => _).join('&') url += args ? '?' + args : '' @@ -935,12 +937,13 @@ const reportUser = ({ credentials, userId, statusIds, comment, forward }) => { }) } -const searchUsers = ({ credentials, query }) => { +const searchUsers = ({ credentials, query, withRelationships }) => { return promisedRequest({ url: MASTODON_USER_SEARCH_URL, params: { q: query, - resolve: true + resolve: true, + with_relationships: withRelationships }, credentials }) @@ -971,6 +974,8 @@ const search2 = ({ credentials, q, resolve, limit, offset, following }) => { params.push(['following', true]) } + params.push(['with_relationships', true]) + let queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&') url += `?${queryString}` -- cgit v1.2.3-70-g09d2 From 355281081a044c950ce7d07fa1081149eb4aec0d Mon Sep 17 00:00:00 2001 From: Shpuld Shpuldson Date: Wed, 13 May 2020 17:53:43 +0300 Subject: don't send undefined --- src/services/api/api.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/services/api/api.service.js') diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 94ff4623..37ccfd6b 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -943,7 +943,7 @@ const searchUsers = ({ credentials, query, withRelationships }) => { params: { q: query, resolve: true, - with_relationships: withRelationships + with_relationships: !!withRelationships }, credentials }) -- cgit v1.2.3-70-g09d2 From 9c7cb3a95431bbea44391f79da465f77565a4b49 Mon Sep 17 00:00:00 2001 From: Shpuld Shpuldson Date: Wed, 13 May 2020 18:04:30 +0300 Subject: remove search1 with_relationships --- src/modules/users.js | 4 ++-- src/services/api/api.service.js | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) (limited to 'src/services/api/api.service.js') diff --git a/src/modules/users.js b/src/modules/users.js index f377da75..f9329f2a 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -428,8 +428,8 @@ const users = { store.commit('setUserForNotification', notification) }) }, - searchUsers (store, { query, withRelationships }) { - return store.rootState.api.backendInteractor.searchUsers({ query, withRelationships }) + searchUsers (store, { query }) { + return store.rootState.api.backendInteractor.searchUsers({ query }) .then((users) => { store.commit('addNewUsers', users) return users diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 37ccfd6b..7f82d2fa 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -937,13 +937,12 @@ const reportUser = ({ credentials, userId, statusIds, comment, forward }) => { }) } -const searchUsers = ({ credentials, query, withRelationships }) => { +const searchUsers = ({ credentials, query }) => { return promisedRequest({ url: MASTODON_USER_SEARCH_URL, params: { q: query, - resolve: true, - with_relationships: !!withRelationships + resolve: true }, credentials }) -- cgit v1.2.3-70-g09d2 From 5235e7ea1e133d699f7e3dc39473d44e789f15c9 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Mon, 25 May 2020 08:48:44 +0300 Subject: Removed `with_muted` param usage for user favorites timeline endpoint (it only supports pagination params). --- src/services/api/api.service.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/services/api/api.service.js') diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 7f82d2fa..9c7530a2 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -538,9 +538,11 @@ const fetchTimeline = ({ if (timeline === 'public' || timeline === 'publicAndExternal') { params.push(['only_media', false]) } + if (timeline !== 'favorites') { + params.push(['with_muted', withMuted]) + } params.push(['limit', 20]) - params.push(['with_muted', withMuted]) const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&') url += `?${queryString}` -- cgit v1.2.3-70-g09d2 From 10070394780ac79e9ee1e8548500586a1f78f65b Mon Sep 17 00:00:00 2001 From: Sergey Suprunenko Date: Sun, 10 May 2020 12:54:55 +0200 Subject: Autocomplete domain mutes from list of known instances --- CHANGELOG.md | 1 + .../domain_mute_card/domain_mute_card.js | 11 +++++++++ .../domain_mute_card/domain_mute_card.vue | 15 ++++++++++++ .../settings_modal/tabs/mutes_and_blocks_tab.js | 28 +++++++++++++++------- .../settings_modal/tabs/mutes_and_blocks_tab.vue | 21 +++++++--------- src/i18n/en.json | 2 +- src/modules/instance.js | 17 +++++++++++++ src/services/api/api.service.js | 6 +++++ 8 files changed, 79 insertions(+), 22 deletions(-) (limited to 'src/services/api/api.service.js') diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e467bc9..11f539da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Add - Added private notifications option for push notifications - 'Copy link' button for statuses (in the ellipsis menu) +- Autocomplete domains from list of known instances ### Changed - Registration page no longer requires email if the server is configured not to require it diff --git a/src/components/domain_mute_card/domain_mute_card.js b/src/components/domain_mute_card/domain_mute_card.js index c8e838ba..f234dcb0 100644 --- a/src/components/domain_mute_card/domain_mute_card.js +++ b/src/components/domain_mute_card/domain_mute_card.js @@ -5,9 +5,20 @@ const DomainMuteCard = { components: { ProgressButton }, + computed: { + user () { + return this.$store.state.users.currentUser + }, + muted () { + return this.user.domainMutes.includes(this.domain) + } + }, methods: { unmuteDomain () { return this.$store.dispatch('unmuteDomain', this.domain) + }, + muteDomain () { + return this.$store.dispatch('muteDomain', this.domain) } } } diff --git a/src/components/domain_mute_card/domain_mute_card.vue b/src/components/domain_mute_card/domain_mute_card.vue index 567d81c5..97aee243 100644 --- a/src/components/domain_mute_card/domain_mute_card.vue +++ b/src/components/domain_mute_card/domain_mute_card.vue @@ -4,6 +4,7 @@ {{ domain }}
    @@ -12,6 +13,16 @@ {{ $t('domain_mute_card.unmute_progress') }} + + {{ $t('domain_mute_card.mute') }} + + @@ -34,5 +45,9 @@ button { width: 10em; } + + .autosuggest-results & { + padding-left: 1em; + } } diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js index b0043dbb..40a87b81 100644 --- a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js +++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js @@ -32,12 +32,12 @@ const DomainMuteList = withSubscription({ const MutesAndBlocks = { data () { return { - activeTab: 'profile', - newDomainToMute: '' + activeTab: 'profile' } }, created () { this.$store.dispatch('fetchTokens') + this.$store.dispatch('getKnownDomains') }, components: { TabSwitcher, @@ -51,6 +51,14 @@ const MutesAndBlocks = { Autosuggest, Checkbox }, + computed: { + knownDomains () { + return this.$store.state.instance.knownDomains + }, + user () { + return this.$store.state.users.currentUser + } + }, methods: { importFollows (file) { return this.$store.state.api.backendInteractor.importFollows({ file }) @@ -86,13 +94,13 @@ const MutesAndBlocks = { filterUnblockedUsers (userIds) { return reject(userIds, (userId) => { const relationship = this.$store.getters.relationship(this.userId) - return relationship.blocking || userId === this.$store.state.users.currentUser.id + return relationship.blocking || userId === this.user.id }) }, filterUnMutedUsers (userIds) { return reject(userIds, (userId) => { const relationship = this.$store.getters.relationship(this.userId) - return relationship.muting || userId === this.$store.state.users.currentUser.id + return relationship.muting || userId === this.user.id }) }, queryUserIds (query) { @@ -111,12 +119,16 @@ const MutesAndBlocks = { unmuteUsers (ids) { return this.$store.dispatch('unmuteUsers', ids) }, + filterUnMutedDomains (urls) { + return urls.filter(url => !this.user.domainMutes.includes(url)) + }, + queryKnownDomains (query) { + return new Promise((resolve, reject) => { + resolve(this.knownDomains.filter(url => url.toLowerCase().includes(query))) + }) + }, unmuteDomains (domains) { return this.$store.dispatch('unmuteDomains', domains) - }, - muteDomain () { - return this.$store.dispatch('muteDomain', this.newDomainToMute) - .then(() => { this.newDomainToMute = '' }) } } } diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue b/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue index 6884b7be..5a1cf2c0 100644 --- a/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue +++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue @@ -119,21 +119,16 @@
    - - - {{ $t('domain_mute_card.mute') }} - - + +
    `/api/v1/pleroma/statuses/${id}/reactions` const PLEROMA_EMOJI_REACT_URL = (id, emoji) => `/api/v1/pleroma/statuses/${id}/reactions/${emoji}` const PLEROMA_EMOJI_UNREACT_URL = (id, emoji) => `/api/v1/pleroma/statuses/${id}/reactions/${emoji}` @@ -995,6 +996,10 @@ const search2 = ({ credentials, q, resolve, limit, offset, following }) => { }) } +const fetchKnownDomains = ({ credentials }) => { + return promisedRequest({ url: MASTODON_KNOWN_DOMAIN_LIST_URL, credentials }) +} + const fetchDomainMutes = ({ credentials }) => { return promisedRequest({ url: MASTODON_DOMAIN_BLOCKS_URL, credentials }) } @@ -1193,6 +1198,7 @@ const apiService = { updateNotificationSettings, search2, searchUsers, + fetchKnownDomains, fetchDomainMutes, muteDomain, unmuteDomain -- cgit v1.2.3-70-g09d2 From 0c364862991907ecd3073503af6b389c305fd53e Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 11 Jun 2020 18:49:39 +0200 Subject: API: Remove fetch polyfill All browser except IE have supported this for longer than Pleroma even exists. --- package.json | 3 +-- src/services/api/api.service.js | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) (limited to 'src/services/api/api.service.js') diff --git a/package.json b/package.json index c131d21a..c0665f6e 100644 --- a/package.json +++ b/package.json @@ -32,8 +32,7 @@ "vue-router": "^3.0.1", "vue-template-compiler": "^2.6.11", "vuelidate": "^0.7.4", - "vuex": "^3.0.1", - "whatwg-fetch": "^2.0.3" + "vuex": "^3.0.1" }, "devDependencies": { "karma-mocha-reporter": "^2.2.1", diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index b3082bc5..dfffc291 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -1,6 +1,5 @@ import { each, map, concat, last, get } from 'lodash' import { parseStatus, parseUser, parseNotification, parseAttachment } from '../entity_normalizer/entity_normalizer.service.js' -import 'whatwg-fetch' import { RegistrationError, StatusCodeError } from '../errors/errors' /* eslint-env browser */ -- cgit v1.2.3-70-g09d2