diff --git a/src/App.scss b/src/App.scss index 921f2c3be..2729e0b01 100644 --- a/src/App.scss +++ b/src/App.scss @@ -648,6 +648,19 @@ nav { border-radius: var(--inputRadius, $fallback--inputRadius); } +.notice-dismissible { + padding-right: 4rem; + position: relative; + + .dismiss { + position: absolute; + top: 0; + right: 0; + padding: .5em; + color: inherit; + } +} + @keyframes modal-background-fadein { from { background-color: rgba(0, 0, 0, 0); diff --git a/src/App.vue b/src/App.vue index cb7e8d785..21abd694e 100644 --- a/src/App.vue +++ b/src/App.vue @@ -18,17 +18,19 @@ -
- -
+
{{ $t('status.repeats') }} diff --git a/src/i18n/en.json b/src/i18n/en.json index 2f48c3892..b4f0deb2e 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -94,6 +94,11 @@ "direct_warning_to_all": "This post will be visible to all the mentioned users.", "direct_warning_to_first_only": "This post will only be visible to the mentioned users at the beginning of the message.", "posting": "Posting", + "scope_notice": { + "public": "This post will be visible to everyone", + "private": "This post will be visible to your followers only", + "unlisted": "This post will not be visible in Public Timeline and The Whole Known Network" + }, "scope": { "direct": "Direct - Post to mentioned users only", "private": "Followers-only - Post to followers only", @@ -233,6 +238,7 @@ "reply_visibility_all": "Show all replies", "reply_visibility_following": "Only show replies directed at me or users I'm following", "reply_visibility_self": "Only show replies directed at me", + "autohide_floating_post_button": "Automatically hide New Post button (mobile)", "saving_err": "Error saving settings", "saving_ok": "Settings saved", "search_user_to_block": "Search whom you want to block", diff --git a/src/i18n/es.json b/src/i18n/es.json index 6c8fd26ac..2e38f8591 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -420,6 +420,7 @@ "muted": "Silenciado", "per_day": "por día", "remote_follow": "Seguir", + "report": "Reportar", "statuses": "Estados", "unblock": "Desbloquear", "unblock_progress": "Desbloqueando...", @@ -451,6 +452,15 @@ "timeline_title": "Linea temporal del usuario", "profile_does_not_exist": "Lo sentimos, este perfil no existe.", "profile_loading_error": "Lo sentimos, hubo un error al cargar este perfil." + }, + "user_reporting": { + "title": "Reportando a {0}", + "add_comment_description": "El informe será enviado a los moderadores de su instancia. Puedes proporcionar una explicación de por qué estás reportando esta cuenta a continuación:", + "additional_comments": "Comentarios adicionales", + "forward_description": "La cuenta es de otro servidor. ¿Enviar una copia del informe allí también?", + "forward_to": "Reenviar a {0}", + "submit": "Enviar", + "generic_error": "Se produjo un error al procesar la solicitud." }, "who_to_follow": { "more": "Más", @@ -477,4 +487,4 @@ "TiB": "TiB" } } -} +} \ No newline at end of file diff --git a/src/i18n/ru.json b/src/i18n/ru.json index 5450f154b..b3ab322d5 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -42,8 +42,13 @@ "attachments_sensitive": "Вложения содержат чувствительный контент", "content_warning": "Тема (не обязательно)", "default": "Что нового?", - "direct_warning": "Этот пост будет видет только упомянутым пользователям", + "direct_warning": "Этот пост будет виден только упомянутым пользователям", "posting": "Отправляется", + "scope_notice": { + "public": "Этот пост будет виден всем", + "private": "Этот пост будет виден только вашим подписчикам", + "unlisted": "Этот пост не будет виден в публичной и федеративной ленте" + }, "scope": { "direct": "Личное - этот пост видят только те кто в нём упомянут", "private": "Для подписчиков - этот пост видят только подписчики", @@ -152,6 +157,7 @@ "reply_visibility_all": "Показывать все ответы", "reply_visibility_following": "Показывать только ответы мне и тех на кого я подписан", "reply_visibility_self": "Показывать только ответы мне", + "autohide_floating_post_button": "Автоматически скрывать кнопку постинга (в мобильной версии)", "saving_err": "Не удалось сохранить настройки", "saving_ok": "Сохранено", "security_tab": "Безопасность", diff --git a/src/modules/config.js b/src/modules/config.js index 1666a2c59..a8da525a0 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -17,6 +17,7 @@ const defaultState = { autoLoad: true, streaming: false, hoverPreview: true, + autohideFloatingPostButton: false, pauseOnUnfocused: true, stopGifs: false, replyVisibility: 'all', @@ -30,6 +31,7 @@ const defaultState = { muteWords: [], highlight: {}, interfaceLanguage: browserLocale, + hideScopeNotice: false, scopeCopy: undefined, // instance default subjectLineBehavior: undefined, // instance default alwaysShowSubjectInput: undefined, // instance default diff --git a/src/modules/statuses.js b/src/modules/statuses.js index 1a223d091..4c92d4e15 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -1,4 +1,4 @@ -import { remove, slice, each, find, maxBy, minBy, merge, first, last, isArray, omitBy } from 'lodash' +import { remove, slice, each, findIndex, find, maxBy, minBy, merge, first, last, isArray, omitBy } from 'lodash' import { set } from 'vue' import apiService from '../services/api/api.service.js' // import parse from '../services/status_parser/status_parser.js' @@ -402,12 +402,27 @@ export const mutations = { }, setFavorited (state, { status, value }) { const newStatus = state.allStatusesObject[status.id] + + if (newStatus.favorited !== value) { + if (value) { + newStatus.fave_num++ + } else { + newStatus.fave_num-- + } + } + newStatus.favorited = value }, - setFavoritedConfirm (state, { status }) { + setFavoritedConfirm (state, { status, user }) { const newStatus = state.allStatusesObject[status.id] newStatus.favorited = status.favorited newStatus.fave_num = status.fave_num + const index = findIndex(newStatus.favoritedBy, { id: user.id }) + if (index !== -1 && !newStatus.favorited) { + newStatus.favoritedBy.splice(index, 1) + } else if (index === -1 && newStatus.favorited) { + newStatus.favoritedBy.push(user) + } }, setRetweeted (state, { status, value }) { const newStatus = state.allStatusesObject[status.id] @@ -422,6 +437,17 @@ export const mutations = { newStatus.repeated = value }, + setRetweetedConfirm (state, { status, user }) { + const newStatus = state.allStatusesObject[status.id] + newStatus.repeated = status.repeated + newStatus.repeat_num = status.repeat_num + const index = findIndex(newStatus.rebloggedBy, { id: user.id }) + if (index !== -1 && !newStatus.repeated) { + newStatus.rebloggedBy.splice(index, 1) + } else if (index === -1 && newStatus.repeated) { + newStatus.rebloggedBy.push(user) + } + }, setDeleted (state, { status }) { const newStatus = state.allStatusesObject[status.id] newStatus.deleted = true @@ -461,11 +487,9 @@ export const mutations = { state.timelines[timeline].flushMarker = id }, addFavsAndRepeats (state, { id, favoritedByUsers, rebloggedByUsers }) { - state.allStatusesObject[id] = { - ...state.allStatusesObject[id], - favoritedBy: favoritedByUsers, - rebloggedBy: rebloggedByUsers - } + const newStatus = state.allStatusesObject[id] + newStatus.favoritedBy = favoritedByUsers.filter(_ => _) + newStatus.rebloggedBy = rebloggedByUsers.filter(_ => _) } } @@ -500,27 +524,26 @@ const statuses = { favorite ({ rootState, commit }, status) { // Optimistic favoriting... commit('setFavorited', { status, value: true }) - apiService.favorite({ id: status.id, credentials: rootState.users.currentUser.credentials }) - .then(status => { - commit('setFavoritedConfirm', { status }) - }) + rootState.api.backendInteractor.favorite(status.id) + .then(status => commit('setFavoritedConfirm', { status, user: rootState.users.currentUser })) }, unfavorite ({ rootState, commit }, status) { - // Optimistic favoriting... + // Optimistic unfavoriting... commit('setFavorited', { status, value: false }) - apiService.unfavorite({ id: status.id, credentials: rootState.users.currentUser.credentials }) - .then(status => { - commit('setFavoritedConfirm', { status }) - }) + rootState.api.backendInteractor.unfavorite(status.id) + .then(status => commit('setFavoritedConfirm', { status, user: rootState.users.currentUser })) }, retweet ({ rootState, commit }, status) { // Optimistic retweeting... commit('setRetweeted', { status, value: true }) - apiService.retweet({ id: status.id, credentials: rootState.users.currentUser.credentials }) + rootState.api.backendInteractor.retweet(status.id) + .then(status => commit('setRetweetedConfirm', { status: status.retweeted_status, user: rootState.users.currentUser })) }, unretweet ({ rootState, commit }, status) { + // Optimistic unretweeting... commit('setRetweeted', { status, value: false }) - apiService.unretweet({ id: status.id, credentials: rootState.users.currentUser.credentials }) + rootState.api.backendInteractor.unretweet(status.id) + .then(status => commit('setRetweetedConfirm', { status, user: rootState.users.currentUser })) }, queueFlush ({ rootState, commit }, { timeline, id }) { commit('queueFlush', { timeline, id }) @@ -537,14 +560,7 @@ const statuses = { rootState.api.backendInteractor.fetchFavoritedByUsers(id), rootState.api.backendInteractor.fetchRebloggedByUsers(id) ]).then(([favoritedByUsers, rebloggedByUsers]) => - commit( - 'addFavsAndRepeats', - { - id, - favoritedByUsers: favoritedByUsers.filter(_ => _), - rebloggedByUsers: rebloggedByUsers.filter(_ => _) - } - ) + commit('addFavsAndRepeats', { id, favoritedByUsers, rebloggedByUsers }) ) } }, diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index da44fc54b..b7a602b8f 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -506,62 +506,22 @@ const verifyCredentials = (user) => { } const favorite = ({ id, credentials }) => { - return fetch(MASTODON_FAVORITE_URL(id), { - headers: authHeaders(credentials), - method: 'POST' - }) - .then(response => { - if (response.ok) { - return response.json() - } else { - throw new Error('Error favoriting post') - } - }) + return promisedRequest({ url: MASTODON_FAVORITE_URL(id), method: 'POST', credentials }) .then((data) => parseStatus(data)) } const unfavorite = ({ id, credentials }) => { - return fetch(MASTODON_UNFAVORITE_URL(id), { - headers: authHeaders(credentials), - method: 'POST' - }) - .then(response => { - if (response.ok) { - return response.json() - } else { - throw new Error('Error removing favorite') - } - }) + return promisedRequest({ url: MASTODON_UNFAVORITE_URL(id), method: 'POST', credentials }) .then((data) => parseStatus(data)) } const retweet = ({ id, credentials }) => { - return fetch(MASTODON_RETWEET_URL(id), { - headers: authHeaders(credentials), - method: 'POST' - }) - .then(response => { - if (response.ok) { - return response.json() - } else { - throw new Error('Error repeating post') - } - }) + return promisedRequest({ url: MASTODON_RETWEET_URL(id), method: 'POST', credentials }) .then((data) => parseStatus(data)) } const unretweet = ({ id, credentials }) => { - return fetch(MASTODON_UNRETWEET_URL(id), { - headers: authHeaders(credentials), - method: 'POST' - }) - .then(response => { - if (response.ok) { - return response.json() - } else { - throw new Error('Error removing repeat') - } - }) + return promisedRequest({ url: MASTODON_UNRETWEET_URL(id), method: 'POST', credentials }) .then((data) => parseStatus(data)) } diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js index 58bb12485..c2b93de4e 100644 --- a/src/services/backend_interactor_service/backend_interactor_service.js +++ b/src/services/backend_interactor_service/backend_interactor_service.js @@ -117,6 +117,11 @@ const backendInteractorService = (credentials) => { const fetchRebloggedByUsers = (id) => apiService.fetchRebloggedByUsers({id}) const reportUser = (params) => apiService.reportUser({credentials, ...params}) + const favorite = (id) => apiService.favorite({id, credentials}) + const unfavorite = (id) => apiService.unfavorite({id, credentials}) + const retweet = (id) => apiService.retweet({id, credentials}) + const unretweet = (id) => apiService.unretweet({id, credentials}) + const backendInteractorServiceInstance = { fetchStatus, fetchConversation, @@ -161,7 +166,11 @@ const backendInteractorService = (credentials) => { denyUser, fetchFavoritedByUsers, fetchRebloggedByUsers, - reportUser + reportUser, + favorite, + unfavorite, + retweet, + unretweet } return backendInteractorServiceInstance diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index 309aa1e8b..7a8708d56 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -309,7 +309,7 @@ export const parseNotification = (data) => { } output.created_at = new Date(data.created_at) - output.id = data.id + output.id = parseInt(data.id) return output }