diff --git a/CHANGELOG.md b/CHANGELOG.md index ff4c2fd1..2719edcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,15 @@ # Changelog All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] ### Added +- Ability to hide/show repeats from user +- User profile button clutter organized into a menu - Emoji picker - Started changelog anew +- Ability to change user's email ### Changed - changed the way fading effects for user profile/long statuses works, now uses css-mask instead of gradient background hacks which weren't exactly compatible with semi-transparent themes ### Fixed diff --git a/src/App.js b/src/App.js index fe63b54c..04a40e30 100644 --- a/src/App.js +++ b/src/App.js @@ -45,7 +45,7 @@ export default { }), created () { // Load the locale from the storage - this.$i18n.locale = this.$store.state.config.interfaceLanguage + this.$i18n.locale = this.$store.getters.mergedConfig.interfaceLanguage window.addEventListener('resize', this.updateMobileState) }, destroyed () { @@ -93,7 +93,7 @@ export default { suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled }, showInstanceSpecificPanel () { return this.$store.state.instance.showInstanceSpecificPanel && - !this.$store.state.config.hideISP && + !this.$store.getters.mergedConfig.hideISP && this.$store.state.instance.instanceSpecificPanelContent }, showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel }, diff --git a/src/App.scss b/src/App.scss index fe271ce1..310962b8 100644 --- a/src/App.scss +++ b/src/App.scss @@ -39,10 +39,13 @@ h4 { text-align: center; } +html { + font-size: 14px; +} + body { font-family: sans-serif; font-family: var(--interfaceFont, sans-serif); - font-size: 14px; margin: 0; color: $fallback--text; color: var(--text, $fallback--text); @@ -717,31 +720,6 @@ nav { } } -@keyframes modal-background-fadein { - from { - background-color: rgba(0, 0, 0, 0); - } - to { - background-color: rgba(0, 0, 0, 0.5); - } -} - -.modal-view { - z-index: 1000; - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - display: flex; - justify-content: center; - align-items: center; - overflow: auto; - animation-duration: 0.2s; - background-color: rgba(0, 0, 0, 0.5); - animation-name: modal-background-fadein; -} - .button-icon { font-size: 1.2em; } diff --git a/src/boot/routes.js b/src/boot/routes.js index cd02711c..5670236c 100644 --- a/src/boot/routes.js +++ b/src/boot/routes.js @@ -47,7 +47,7 @@ export default (store) => { { name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute }, { name: 'settings', path: '/settings', component: Settings }, { name: 'registration', path: '/registration', component: Registration }, - { name: 'password-reset', path: '/password-reset', component: PasswordReset }, + { name: 'password-reset', path: '/password-reset', component: PasswordReset, props: true }, { name: 'registration-token', path: '/registration/:token', component: Registration }, { name: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute }, { name: 'user-settings', path: '/user-settings', component: UserSettings, beforeEnter: validateAuthenticatedRoute }, diff --git a/src/components/account_actions/account_actions.js b/src/components/account_actions/account_actions.js new file mode 100644 index 00000000..204d506a --- /dev/null +++ b/src/components/account_actions/account_actions.js @@ -0,0 +1,35 @@ +import ProgressButton from '../progress_button/progress_button.vue' + +const AccountActions = { + props: [ + 'user' + ], + data () { + return { } + }, + components: { + ProgressButton + }, + methods: { + showRepeats () { + this.$store.dispatch('showReblogs', this.user.id) + }, + hideRepeats () { + this.$store.dispatch('hideReblogs', this.user.id) + }, + blockUser () { + this.$store.dispatch('blockUser', this.user.id) + }, + unblockUser () { + this.$store.dispatch('unblockUser', this.user.id) + }, + reportUser () { + this.$store.dispatch('openUserReportingModal', this.user.id) + }, + mentionUser () { + this.$store.dispatch('openPostStatusModal', { replyTo: true, repliedUser: this.user }) + } + } +} + +export default AccountActions diff --git a/src/components/account_actions/account_actions.vue b/src/components/account_actions/account_actions.vue new file mode 100644 index 00000000..046cba93 --- /dev/null +++ b/src/components/account_actions/account_actions.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js index e93921fe..06b496b0 100644 --- a/src/components/attachment/attachment.js +++ b/src/components/attachment/attachment.js @@ -10,13 +10,14 @@ const Attachment = { 'statusId', 'size', 'allowPlay', - 'setMedia' + 'setMedia', + 'naturalSizeLoad' ], data () { return { nsfwImage: this.$store.state.instance.nsfwCensorImage || nsfwImage, - hideNsfwLocal: this.$store.state.config.hideNsfw, - preloadImage: this.$store.state.config.preloadImage, + hideNsfwLocal: this.$store.getters.mergedConfig.hideNsfw, + preloadImage: this.$store.getters.mergedConfig.preloadImage, loading: false, img: fileTypeService.fileType(this.attachment.mimetype) === 'image' && document.createElement('img'), modalOpen: false, @@ -57,7 +58,7 @@ const Attachment = { } }, openModal (event) { - const modalTypes = this.$store.state.config.playVideosInModal + const modalTypes = this.$store.getters.mergedConfig.playVideosInModal ? ['image', 'video'] : ['image'] if (fileTypeService.fileMatchesSomeType(modalTypes, this.attachment) || @@ -70,7 +71,7 @@ const Attachment = { } }, toggleHidden (event) { - if (this.$store.state.config.useOneClickNsfw && !this.showHidden) { + if (this.$store.getters.mergedConfig.useOneClickNsfw && !this.showHidden) { this.openModal(event) return } @@ -88,6 +89,11 @@ const Attachment = { } else { this.showHidden = !this.showHidden } + }, + onImageLoad (image) { + const width = image.naturalWidth + const height = image.naturalHeight + this.naturalSizeLoad && this.naturalSizeLoad({ width, height }) } } } diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue index af16e302..0748b2f0 100644 --- a/src/components/attachment/attachment.vue +++ b/src/components/attachment/attachment.vue @@ -58,6 +58,7 @@ :referrerpolicy="referrerpolicy" :mimetype="attachment.mimetype" :src="attachment.large_thumb_url || attachment.url" + :image-load-handler="onImageLoad" /> diff --git a/src/components/checkbox/checkbox.vue b/src/components/checkbox/checkbox.vue index 2b822ec3..5917598a 100644 --- a/src/components/checkbox/checkbox.vue +++ b/src/components/checkbox/checkbox.vue @@ -1,13 +1,22 @@ @@ -17,7 +26,11 @@ export default { prop: 'checked', event: 'change' }, - props: ['checked', 'indeterminate'] + props: [ + 'checked', + 'indeterminate', + 'disabled' + ] } @@ -27,12 +40,16 @@ export default { .checkbox { position: relative; display: inline-block; - padding-left: 1.2em; min-height: 1.2em; + &-indicator { + position: relative; + padding-left: 1.2em; + } + &-indicator::before { position: absolute; - left: 0; + right: 0; top: 0; display: block; content: '✔'; @@ -54,6 +71,17 @@ export default { box-sizing: border-box; } + &.disabled { + .checkbox-indicator::before, + .label { + opacity: .5; + } + .label { + color: $fallback--faint; + color: var(--faint, $fallback--faint); + } + } + input[type=checkbox] { display: none; @@ -68,9 +96,6 @@ export default { color: var(--text, $fallback--text); } - &:disabled + .checkbox-indicator::before { - opacity: .5; - } } & > span { diff --git a/src/components/emoji_input/emoji_input.js b/src/components/emoji_input/emoji_input.js index 2c73faa7..001a22e9 100644 --- a/src/components/emoji_input/emoji_input.js +++ b/src/components/emoji_input/emoji_input.js @@ -99,7 +99,7 @@ const EmojiInput = { }, computed: { padEmoji () { - return this.$store.state.config.padEmoji + return this.$store.getters.mergedConfig.padEmoji }, suggestions () { const firstchar = this.textAtCaret.charAt(0) diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js index 0d5ffc9b..76d1f26b 100644 --- a/src/components/emoji_picker/emoji_picker.js +++ b/src/components/emoji_picker/emoji_picker.js @@ -1,4 +1,5 @@ import { set } from 'vue' +import Checkbox from '../checkbox/checkbox.vue' const LOAD_EMOJI_BY = 50 const LOAD_EMOJI_INTERVAL = 100 @@ -18,7 +19,6 @@ const EmojiPicker = { }, data () { return { - labelKey: String(Math.random() * 100000), keyword: '', activeGroup: 'custom', showingStickers: false, @@ -32,7 +32,8 @@ const EmojiPicker = { } }, components: { - StickerPicker: () => import('../sticker_picker/sticker_picker.vue') + StickerPicker: () => import('../sticker_picker/sticker_picker.vue'), + Checkbox }, methods: { onEmoji (emoji) { diff --git a/src/components/emoji_picker/emoji_picker.vue b/src/components/emoji_picker/emoji_picker.vue index e46a5ec1..43da6aa2 100644 --- a/src/components/emoji_picker/emoji_picker.vue +++ b/src/components/emoji_picker/emoji_picker.vue @@ -75,22 +75,10 @@ -
- - +
+ + {{ $t('emoji.keep_open') }} +
- {{ status.fave_num }} + {{ status.fave_num }}
diff --git a/src/components/follow_button/follow_button.js b/src/components/follow_button/follow_button.js new file mode 100644 index 00000000..12da2645 --- /dev/null +++ b/src/components/follow_button/follow_button.js @@ -0,0 +1,53 @@ +import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate' +export default { + props: ['user', 'labelFollowing', 'buttonClass'], + data () { + return { + inProgress: false + } + }, + computed: { + isPressed () { + return this.inProgress || this.user.following + }, + title () { + if (this.inProgress || this.user.following) { + return this.$t('user_card.follow_unfollow') + } else if (this.user.requested) { + return this.$t('user_card.follow_again') + } else { + return this.$t('user_card.follow') + } + }, + label () { + if (this.inProgress) { + return this.$t('user_card.follow_progress') + } else if (this.user.following) { + return this.labelFollowing || this.$t('user_card.following') + } else if (this.user.requested) { + return this.$t('user_card.follow_sent') + } else { + return this.$t('user_card.follow') + } + } + }, + methods: { + onClick () { + this.user.following ? this.unfollow() : this.follow() + }, + follow () { + this.inProgress = true + requestFollow(this.user, this.$store).then(() => { + this.inProgress = false + }) + }, + unfollow () { + const store = this.$store + this.inProgress = true + requestUnfollow(this.user, store).then(() => { + this.inProgress = false + store.commit('removeStatus', { timeline: 'friends', userId: this.user.id }) + }) + } + } +} diff --git a/src/components/follow_button/follow_button.vue b/src/components/follow_button/follow_button.vue new file mode 100644 index 00000000..f0cbb94b --- /dev/null +++ b/src/components/follow_button/follow_button.vue @@ -0,0 +1,13 @@ + + + diff --git a/src/components/follow_card/follow_card.js b/src/components/follow_card/follow_card.js index dc4a0d41..aefd609e 100644 --- a/src/components/follow_card/follow_card.js +++ b/src/components/follow_card/follow_card.js @@ -1,21 +1,16 @@ import BasicUserCard from '../basic_user_card/basic_user_card.vue' import RemoteFollow from '../remote_follow/remote_follow.vue' -import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate' +import FollowButton from '../follow_button/follow_button.vue' const FollowCard = { props: [ 'user', 'noFollowsYou' ], - data () { - return { - inProgress: false, - requestSent: false - } - }, components: { BasicUserCard, - RemoteFollow + RemoteFollow, + FollowButton }, computed: { isMe () { @@ -24,21 +19,6 @@ const FollowCard = { loggedIn () { return this.$store.state.users.currentUser } - }, - methods: { - followUser () { - this.inProgress = true - requestFollow(this.user, this.$store).then(({ sent }) => { - this.inProgress = false - this.requestSent = sent - }) - }, - unfollowUser () { - this.inProgress = true - requestUnfollow(this.user, this.$store).then(() => { - this.inProgress = false - }) - } } } diff --git a/src/components/follow_card/follow_card.vue b/src/components/follow_card/follow_card.vue index 310fe843..81e6e6dc 100644 --- a/src/components/follow_card/follow_card.vue +++ b/src/components/follow_card/follow_card.vue @@ -16,36 +16,11 @@
diff --git a/src/components/gallery/gallery.js b/src/components/gallery/gallery.js index 7f33a81b..f856fd0a 100644 --- a/src/components/gallery/gallery.js +++ b/src/components/gallery/gallery.js @@ -1,23 +1,18 @@ import Attachment from '../attachment/attachment.vue' -import { chunk, last, dropRight } from 'lodash' +import { chunk, last, dropRight, sumBy } from 'lodash' const Gallery = { - data: () => ({ - width: 500 - }), props: [ 'attachments', 'nsfw', 'setMedia' ], + data () { + return { + sizes: {} + } + }, components: { Attachment }, - mounted () { - this.resize() - window.addEventListener('resize', this.resize) - }, - destroyed () { - window.removeEventListener('resize', this.resize) - }, computed: { rows () { if (!this.attachments) { @@ -33,21 +28,24 @@ const Gallery = { } return rows }, - rowHeight () { - return itemsPerRow => ({ 'height': `${(this.width / (itemsPerRow + 0.6))}px` }) - }, useContainFit () { - return this.$store.state.config.useContainFit + return this.$store.getters.mergedConfig.useContainFit } }, methods: { - resize () { - // Quick optimization to make resizing not always trigger state change, - // only update attachment size in 10px steps - const width = Math.floor(this.$el.getBoundingClientRect().width / 10) * 10 - if (this.width !== width) { - this.width = width - } + onNaturalSizeLoad (id, size) { + this.$set(this.sizes, id, size) + }, + rowStyle (itemsPerRow) { + return { 'padding-bottom': `${(100 / (itemsPerRow + 0.6))}%` } + }, + itemStyle (id, row) { + const total = sumBy(row, item => this.getAspectRatio(item.id)) + return { flex: `${this.getAspectRatio(id) / total} 1 0%` } + }, + getAspectRatio (id) { + const size = this.sizes[id] + return size ? size.width / size.height : 1 } } } diff --git a/src/components/gallery/gallery.vue b/src/components/gallery/gallery.vue index 6169d294..7abc2161 100644 --- a/src/components/gallery/gallery.vue +++ b/src/components/gallery/gallery.vue @@ -7,17 +7,21 @@ v-for="(row, index) in rows" :key="index" class="gallery-row" - :style="rowHeight(row.length)" + :style="rowStyle(row.length)" :class="{ 'contain-fit': useContainFit, 'cover-fit': !useContainFit }" > - +
@@ -28,15 +32,24 @@ @import '../../_variables.scss'; .gallery-row { - height: 200px; + position: relative; + height: 0; width: 100%; - display: flex; - flex-direction: row; - flex-wrap: nowrap; - align-content: stretch; flex-grow: 1; margin-top: 0.5em; + .gallery-row-inner { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-content: stretch; + } + // FIXME: specificity problem with this and .attachments.attachment // we shouldn't have the need for .image here .attachment.image { diff --git a/src/components/interface_language_switcher/interface_language_switcher.vue b/src/components/interface_language_switcher/interface_language_switcher.vue index 83df9a0b..1ca22001 100644 --- a/src/components/interface_language_switcher/interface_language_switcher.vue +++ b/src/components/interface_language_switcher/interface_language_switcher.vue @@ -40,7 +40,7 @@ export default { }, language: { - get: function () { return this.$store.state.config.interfaceLanguage }, + get: function () { return this.$store.getters.mergedConfig.interfaceLanguage }, set: function (val) { this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val }) this.$i18n.locale = val diff --git a/src/components/login_form/login_form.js b/src/components/login_form/login_form.js index 10f52fe2..0b574a04 100644 --- a/src/components/login_form/login_form.js +++ b/src/components/login_form/login_form.js @@ -59,6 +59,8 @@ const LoginForm = { if (result.error) { if (result.error === 'mfa_required') { this.requireMFA({ app: app, settings: result }) + } else if (result.identifier === 'password_reset_required') { + this.$router.push({ name: 'password-reset', params: { passwordResetRequested: true } }) } else { this.error = result.error this.focusOnPasswordInput() diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js index 992d7129..abb18c7d 100644 --- a/src/components/media_modal/media_modal.js +++ b/src/components/media_modal/media_modal.js @@ -1,11 +1,14 @@ import StillImage from '../still-image/still-image.vue' import VideoAttachment from '../video_attachment/video_attachment.vue' +import Modal from '../modal/modal.vue' import fileTypeService from '../../services/file_type/file_type.service.js' +import GestureService from '../../services/gesture_service/gesture_service' const MediaModal = { components: { StillImage, - VideoAttachment + VideoAttachment, + Modal }, computed: { showing () { @@ -27,7 +30,27 @@ const MediaModal = { return this.currentMedia ? fileTypeService.fileType(this.currentMedia.mimetype) : null } }, + created () { + this.mediaSwipeGestureRight = GestureService.swipeGesture( + GestureService.DIRECTION_RIGHT, + this.goPrev, + 50 + ) + this.mediaSwipeGestureLeft = GestureService.swipeGesture( + GestureService.DIRECTION_LEFT, + this.goNext, + 50 + ) + }, methods: { + mediaTouchStart (e) { + GestureService.beginSwipe(e, this.mediaSwipeGestureRight) + GestureService.beginSwipe(e, this.mediaSwipeGestureLeft) + }, + mediaTouchMove (e) { + GestureService.updateSwipe(e, this.mediaSwipeGestureRight) + GestureService.updateSwipe(e, this.mediaSwipeGestureLeft) + }, hide () { this.$store.dispatch('closeMediaViewer') }, diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue index 06ced5a1..49e3143e 100644 --- a/src/components/media_modal/media_modal.vue +++ b/src/components/media_modal/media_modal.vue @@ -1,14 +1,15 @@ diff --git a/src/components/mobile_nav/mobile_nav.js b/src/components/mobile_nav/mobile_nav.js index c2bb76ee..5a90c31f 100644 --- a/src/components/mobile_nav/mobile_nav.js +++ b/src/components/mobile_nav/mobile_nav.js @@ -63,7 +63,7 @@ const MobileNav = { this.$refs.notifications.markAsSeen() }, onScroll ({ target: { scrollTop, clientHeight, scrollHeight } }) { - if (this.$store.state.config.autoLoad && scrollTop + clientHeight >= scrollHeight) { + if (this.$store.getters.mergedConfig.autoLoad && scrollTop + clientHeight >= scrollHeight) { this.$refs.notifications.fetchOlderNotifications() } } diff --git a/src/components/mobile_post_status_button/mobile_post_status_button.js b/src/components/mobile_post_status_button/mobile_post_status_button.js index 3e77148a..0ad12bb1 100644 --- a/src/components/mobile_post_status_button/mobile_post_status_button.js +++ b/src/components/mobile_post_status_button/mobile_post_status_button.js @@ -30,7 +30,7 @@ const MobilePostStatusButton = { return this.autohideFloatingPostButton && (this.hidden || this.inputActive) }, autohideFloatingPostButton () { - return !!this.$store.state.config.autohideFloatingPostButton + return !!this.$store.getters.mergedConfig.autohideFloatingPostButton } }, watch: { diff --git a/src/components/modal/modal.vue b/src/components/modal/modal.vue new file mode 100644 index 00000000..cee24241 --- /dev/null +++ b/src/components/modal/modal.vue @@ -0,0 +1,52 @@ + + + + + diff --git a/src/components/moderation_tools/moderation_tools.vue b/src/components/moderation_tools/moderation_tools.vue index d97ca3aa..006d6373 100644 --- a/src/components/moderation_tools/moderation_tools.vue +++ b/src/components/moderation_tools/moderation_tools.vue @@ -3,9 +3,7 @@ diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js index 8e817f3b..7d46eb5a 100644 --- a/src/components/notification/notification.js +++ b/src/components/notification/notification.js @@ -39,7 +39,7 @@ const Notification = { return highlightClass(this.notification.from_profile) }, userStyle () { - const highlight = this.$store.state.config.highlight + const highlight = this.$store.getters.mergedConfig.highlight const user = this.notification.from_profile return highlightStyle(highlight[user.screen_name]) }, diff --git a/src/components/password_reset/password_reset.js b/src/components/password_reset/password_reset.js index fa71e07a..62e74e30 100644 --- a/src/components/password_reset/password_reset.js +++ b/src/components/password_reset/password_reset.js @@ -25,6 +25,12 @@ const passwordReset = { this.$router.push({ name: 'root' }) } }, + props: { + passwordResetRequested: { + default: false, + type: Boolean + } + }, methods: { dismissError () { this.error = null diff --git a/src/components/password_reset/password_reset.vue b/src/components/password_reset/password_reset.vue index 00474e95..713c9dce 100644 --- a/src/components/password_reset/password_reset.vue +++ b/src/components/password_reset/password_reset.vue @@ -10,7 +10,10 @@ >
-

+

+ {{ $t('password_reset.password_reset_required_but_mailer_is_disabled') }} +

+

{{ $t('password_reset.password_reset_disabled') }}

@@ -25,6 +28,12 @@
+

+ {{ $t('password_reset.password_reset_required') }} +

{{ $t('password_reset.instruction') }}

@@ -104,6 +113,11 @@ margin: 0.3em 0.0em 1em; } + .password-reset-required { + background-color: var(--alertError, $fallback--alertError); + padding: 10px 0; + } + .notice-dismissible { padding-right: 2rem; } diff --git a/src/components/popper/popper.scss b/src/components/popper/popper.scss index 279b01be..06daa871 100644 --- a/src/components/popper/popper.scss +++ b/src/components/popper/popper.scss @@ -20,7 +20,6 @@ margin: 5px; border-color: $fallback--bg; border-color: var(--bg, $fallback--bg); - z-index: 1; } &[x-placement^="top"] { @@ -31,7 +30,7 @@ border-left-color: transparent !important; border-right-color: transparent !important; border-bottom-color: transparent !important; - bottom: -5px; + bottom: -4px; left: calc(50% - 5px); margin-top: 0; margin-bottom: 0; @@ -46,7 +45,7 @@ border-left-color: transparent !important; border-right-color: transparent !important; border-top-color: transparent !important; - top: -5px; + top: -4px; left: calc(50% - 5px); margin-top: 0; margin-bottom: 0; @@ -61,7 +60,7 @@ border-left-color: transparent !important; border-top-color: transparent !important; border-bottom-color: transparent !important; - left: -5px; + left: -4px; top: calc(50% - 5px); margin-left: 0; margin-right: 0; @@ -76,7 +75,7 @@ border-top-color: transparent !important; border-right-color: transparent !important; border-bottom-color: transparent !important; - right: -5px; + right: -4px; top: calc(50% - 5px); margin-left: 0; margin-right: 0; diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 483c395e..af6299e4 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -7,6 +7,8 @@ import fileTypeService from '../../services/file_type/file_type.service.js' import { findOffset } from '../../services/offset_finder/offset_finder.service.js' import { reject, map, uniqBy } from 'lodash' import suggestor from '../emoji_input/suggestor.js' +import { mapGetters } from 'vuex' +import Checkbox from '../checkbox/checkbox.vue' const buildMentionsString = ({ user, attentions = [] }, currentUser) => { let allAttentions = [...attentions] @@ -35,7 +37,8 @@ const PostStatusForm = { MediaUpload, EmojiInput, PollForm, - ScopeSelector + ScopeSelector, + Checkbox }, mounted () { this.resize(this.$refs.textarea) @@ -50,9 +53,7 @@ const PostStatusForm = { const preset = this.$route.query.message let statusText = preset || '' - const scopeCopy = typeof this.$store.state.config.scopeCopy === 'undefined' - ? this.$store.state.instance.scopeCopy - : this.$store.state.config.scopeCopy + const { scopeCopy } = this.$store.getters.mergedConfig if (this.replyTo) { const currentUser = this.$store.state.users.currentUser @@ -63,9 +64,7 @@ const PostStatusForm = { ? this.copyMessageScope : this.$store.state.users.currentUser.default_scope - const contentType = typeof this.$store.state.config.postContentType === 'undefined' - ? this.$store.state.instance.postContentType - : this.$store.state.config.postContentType + const { postContentType: contentType } = this.$store.getters.mergedConfig return { dropFiles: [], @@ -94,10 +93,7 @@ const PostStatusForm = { return this.$store.state.users.currentUser.default_scope }, showAllScopes () { - const minimalScopesMode = typeof this.$store.state.config.minimalScopesMode === 'undefined' - ? this.$store.state.instance.minimalScopesMode - : this.$store.state.config.minimalScopesMode - return !minimalScopesMode + return !this.mergedConfig.minimalScopesMode }, emojiUserSuggestor () { return suggestor({ @@ -145,13 +141,7 @@ const PostStatusForm = { return this.$store.state.instance.minimalScopesMode }, alwaysShowSubject () { - if (typeof this.$store.state.config.alwaysShowSubjectInput !== 'undefined') { - return this.$store.state.config.alwaysShowSubjectInput - } else if (typeof this.$store.state.instance.alwaysShowSubjectInput !== 'undefined') { - return this.$store.state.instance.alwaysShowSubjectInput - } else { - return true - } + return this.mergedConfig.alwaysShowSubjectInput }, postFormats () { return this.$store.state.instance.postFormats || [] @@ -164,13 +154,14 @@ const PostStatusForm = { this.$store.state.instance.pollLimits.max_options >= 2 }, hideScopeNotice () { - return this.$store.state.config.hideScopeNotice + return this.$store.getters.mergedConfig.hideScopeNotice }, pollContentError () { return this.pollFormVisible && this.newStatus.poll && this.newStatus.poll.error - } + }, + ...mapGetters(['mergedConfig']) }, methods: { postStatus (newStatus) { diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue index 60dcf90c..13e8b0aa 100644 --- a/src/components/post_status_form/post_status_form.vue +++ b/src/components/post_status_form/post_status_form.vue @@ -264,12 +264,9 @@ v-if="newStatus.files.length > 0" class="upload_settings" > - - + + {{ $t('post_status.attachments_sensitive') }} +
diff --git a/src/components/post_status_modal/post_status_modal.js b/src/components/post_status_modal/post_status_modal.js index 38258296..b44354db 100644 --- a/src/components/post_status_modal/post_status_modal.js +++ b/src/components/post_status_modal/post_status_modal.js @@ -1,9 +1,11 @@ import PostStatusForm from '../post_status_form/post_status_form.vue' +import Modal from '../modal/modal.vue' import get from 'lodash/get' const PostStatusModal = { components: { - PostStatusForm + PostStatusForm, + Modal }, data () { return { diff --git a/src/components/post_status_modal/post_status_modal.vue b/src/components/post_status_modal/post_status_modal.vue index d3a82389..dbcd321e 100644 --- a/src/components/post_status_modal/post_status_modal.vue +++ b/src/components/post_status_modal/post_status_modal.vue @@ -1,14 +1,11 @@ diff --git a/src/components/still-image/still-image.js b/src/components/still-image/still-image.js index 02e98f19..e48fef47 100644 --- a/src/components/still-image/still-image.js +++ b/src/components/still-image/still-image.js @@ -3,11 +3,12 @@ const StillImage = { 'src', 'referrerpolicy', 'mimetype', - 'imageLoadError' + 'imageLoadError', + 'imageLoadHandler' ], data () { return { - stopGifs: this.$store.state.config.stopGifs + stopGifs: this.$store.getters.mergedConfig.stopGifs } }, computed: { @@ -17,6 +18,7 @@ const StillImage = { }, methods: { onLoad () { + this.imageLoadHandler && this.imageLoadHandler(this.$refs.src) const canvas = this.$refs.canvas if (!canvas) return const width = this.$refs.src.naturalWidth diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js index ef5704c1..ebde4475 100644 --- a/src/components/style_switcher/style_switcher.js +++ b/src/components/style_switcher/style_switcher.js @@ -10,6 +10,7 @@ import ContrastRatio from '../contrast_ratio/contrast_ratio.vue' import TabSwitcher from '../tab_switcher/tab_switcher.js' import Preview from './preview.vue' import ExportImport from '../export_import/export_import.vue' +import Checkbox from '../checkbox/checkbox.vue' // List of color values used in v1 const v1OnlyNames = [ @@ -27,7 +28,7 @@ export default { data () { return { availableStyles: [], - selected: this.$store.state.config.theme, + selected: this.$store.getters.mergedConfig.theme, previewShadows: {}, previewColors: {}, @@ -112,7 +113,7 @@ export default { }) }, mounted () { - this.normalizeLocalState(this.$store.state.config.customTheme) + this.normalizeLocalState(this.$store.getters.mergedConfig.customTheme) if (typeof this.shadowSelected === 'undefined') { this.shadowSelected = this.shadowsAvailable[0] } @@ -341,7 +342,8 @@ export default { FontControl, TabSwitcher, Preview, - ExportImport + ExportImport, + Checkbox }, methods: { setCustomTheme () { @@ -368,9 +370,9 @@ export default { return version >= 1 || version <= 2 }, clearAll () { - const state = this.$store.state.config.customTheme + const state = this.$store.getters.mergedConfig.customTheme const version = state.colors ? 2 : 'l1' - this.normalizeLocalState(this.$store.state.config.customTheme, version) + this.normalizeLocalState(this.$store.getters.mergedConfig.customTheme, version) }, // Clears all the extra stuff when loading V1 theme diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue index d32641c2..ad032041 100644 --- a/src/components/style_switcher/style_switcher.vue +++ b/src/components/style_switcher/style_switcher.vue @@ -42,44 +42,29 @@
- - + + {{ $t('settings.style.switcher.keep_color') }} + - - + + {{ $t('settings.style.switcher.keep_shadows') }} + - - + + {{ $t('settings.style.switcher.keep_opacity') }} + - - + + {{ $t('settings.style.switcher.keep_roundness') }} + - - + + {{ $t('settings.style.switcher.keep_fonts') }} +

{{ $t('settings.style.switcher.save_load_hint') }}

diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index 0594576c..27a9a55e 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -141,7 +141,7 @@ const Timeline = { const bodyBRect = document.body.getBoundingClientRect() const height = Math.max(bodyBRect.height, -(bodyBRect.y)) if (this.timeline.loading === false && - this.$store.state.config.autoLoad && + this.$store.getters.mergedConfig.autoLoad && this.$el.offsetHeight > 0 && (window.innerHeight + window.pageYOffset) >= (height - 750)) { this.fetchOlderStatuses() @@ -153,7 +153,7 @@ const Timeline = { }, watch: { newStatusCount (count) { - if (!this.$store.state.config.streaming) { + if (!this.$store.getters.mergedConfig.streaming) { return } if (count > 0) { @@ -162,7 +162,7 @@ const Timeline = { const top = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0) if (top < 15 && !this.paused && - !(this.unfocused && this.$store.state.config.pauseOnUnfocused) + !(this.unfocused && this.$store.getters.mergedConfig.pauseOnUnfocused) ) { this.showNewStatuses() } else { diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js index 9c931c01..cc8a1ed6 100644 --- a/src/components/user_card/user_card.js +++ b/src/components/user_card/user_card.js @@ -1,19 +1,20 @@ import UserAvatar from '../user_avatar/user_avatar.vue' import RemoteFollow from '../remote_follow/remote_follow.vue' import ProgressButton from '../progress_button/progress_button.vue' +import FollowButton from '../follow_button/follow_button.vue' import ModerationTools from '../moderation_tools/moderation_tools.vue' +import AccountActions from '../account_actions/account_actions.vue' import { hex2rgb } from '../../services/color_convert/color_convert.js' -import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' +import { mapGetters } from 'vuex' export default { - props: [ 'user', 'switcher', 'selected', 'hideBio', 'rounded', 'bordered', 'allowZoomingAvatar' ], + props: [ + 'user', 'switcher', 'selected', 'hideBio', 'rounded', 'bordered', 'allowZoomingAvatar' + ], data () { return { followRequestInProgress: false, - hideUserStatsLocal: typeof this.$store.state.config.hideUserStats === 'undefined' - ? this.$store.state.instance.hideUserStats - : this.$store.state.config.hideUserStats, betterShadow: this.$store.state.interface.browserSupport.cssFilter } }, @@ -29,9 +30,9 @@ export default { }] }, style () { - const color = this.$store.state.config.customTheme.colors - ? this.$store.state.config.customTheme.colors.bg // v2 - : this.$store.state.config.colors.bg // v1 + const color = this.$store.getters.mergedConfig.customTheme.colors + ? this.$store.getters.mergedConfig.customTheme.colors.bg // v2 + : this.$store.getters.mergedConfig.colors.bg // v1 if (color) { const rgb = (typeof color === 'string') ? hex2rgb(color) : color @@ -63,21 +64,22 @@ export default { }, userHighlightType: { get () { - const data = this.$store.state.config.highlight[this.user.screen_name] + const data = this.$store.getters.mergedConfig.highlight[this.user.screen_name] return (data && data.type) || 'disabled' }, set (type) { - const data = this.$store.state.config.highlight[this.user.screen_name] + const data = this.$store.getters.mergedConfig.highlight[this.user.screen_name] if (type !== 'disabled') { this.$store.dispatch('setHighlight', { user: this.user.screen_name, color: (data && data.color) || '#FFFFFF', type }) } else { this.$store.dispatch('setHighlight', { user: this.user.screen_name, color: undefined }) } - } + }, + ...mapGetters(['mergedConfig']) }, userHighlightColor: { get () { - const data = this.$store.state.config.highlight[this.user.screen_name] + const data = this.$store.getters.mergedConfig.highlight[this.user.screen_name] return data && data.color }, set (color) { @@ -90,36 +92,18 @@ export default { const validRole = rights.admin || rights.moderator const roleTitle = rights.admin ? 'admin' : 'moderator' return validRole && roleTitle - } + }, + ...mapGetters(['mergedConfig']) }, components: { UserAvatar, RemoteFollow, ModerationTools, - ProgressButton + AccountActions, + ProgressButton, + FollowButton }, methods: { - followUser () { - const store = this.$store - this.followRequestInProgress = true - requestFollow(this.user, store).then(() => { - this.followRequestInProgress = false - }) - }, - unfollowUser () { - const store = this.$store - this.followRequestInProgress = true - requestUnfollow(this.user, store).then(() => { - this.followRequestInProgress = false - store.commit('removeStatus', { timeline: 'friends', userId: this.user.id }) - }) - }, - blockUser () { - this.$store.dispatch('blockUser', this.user.id) - }, - unblockUser () { - this.$store.dispatch('unblockUser', this.user.id) - }, muteUser () { this.$store.dispatch('muteUser', this.user.id) }, @@ -147,10 +131,10 @@ export default { } }, userProfileLink (user) { - return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames) - }, - reportUser () { - this.$store.dispatch('openUserReportingModal', this.user.id) + return generateProfileLink( + user.id, user.screen_name, + this.$store.state.instance.restrictedNicknames + ) }, zoomAvatar () { const attachment = { @@ -159,9 +143,6 @@ export default { } this.$store.dispatch('setMedia', [attachment]) this.$store.dispatch('setCurrent', attachment) - }, - mentionUser () { - this.$store.dispatch('openPostStatusModal', { replyTo: true, repliedUser: this.user }) } } } diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue index 5b6f66e7..6f3c958e 100644 --- a/src/components/user_card/user_card.vue +++ b/src/components/user_card/user_card.vue @@ -66,8 +66,11 @@ > + -
{{ visibleRole }} {{ dailyAvg }} {{ $t('user_card.per_day') }}
@@ -135,72 +138,27 @@ v-if="loggedIn && isOtherUser" class="user-interactions" > -
- +
+ +
-
- -
-
- - - - - - - -
- -
- -
-
- -
- - -
- -
- -
-
* { - flex: 1 0 0; margin: 0 .75em .6em 0; white-space: nowrap; + min-width: 95px; } button { diff --git a/src/components/user_reporting_modal/user_reporting_modal.js b/src/components/user_reporting_modal/user_reporting_modal.js index 7c6ea409..833fa98a 100644 --- a/src/components/user_reporting_modal/user_reporting_modal.js +++ b/src/components/user_reporting_modal/user_reporting_modal.js @@ -2,12 +2,14 @@ import Status from '../status/status.vue' import List from '../list/list.vue' import Checkbox from '../checkbox/checkbox.vue' +import Modal from '../modal/modal.vue' const UserReportingModal = { components: { Status, List, - Checkbox + Checkbox, + Modal }, data () { return { diff --git a/src/components/user_reporting_modal/user_reporting_modal.vue b/src/components/user_reporting_modal/user_reporting_modal.vue index c79a3707..6ee53461 100644 --- a/src/components/user_reporting_modal/user_reporting_modal.vue +++ b/src/components/user_reporting_modal/user_reporting_modal.vue @@ -1,13 +1,9 @@ diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js index f12cccae..3fdc5340 100644 --- a/src/components/user_settings/user_settings.js +++ b/src/components/user_settings/user_settings.js @@ -17,6 +17,7 @@ import Autosuggest from '../autosuggest/autosuggest.vue' import Importer from '../importer/importer.vue' import Exporter from '../exporter/exporter.vue' import withSubscription from '../../hocs/with_subscription/with_subscription' +import Checkbox from '../checkbox/checkbox.vue' import Mfa from './mfa.vue' const BlockList = withSubscription({ @@ -34,6 +35,7 @@ const MuteList = withSubscription({ const UserSettings = { data () { return { + newEmail: '', newName: this.$store.state.users.currentUser.name, newBio: unescape(this.$store.state.users.currentUser.description), newLocked: this.$store.state.users.currentUser.locked, @@ -55,6 +57,9 @@ const UserSettings = { backgroundPreview: null, bannerUploadError: null, backgroundUploadError: null, + changeEmailError: false, + changeEmailPassword: '', + changedEmail: false, deletingAccount: false, deleteAccountConfirmPasswordInput: '', deleteAccountError: false, @@ -82,7 +87,8 @@ const UserSettings = { ProgressButton, Importer, Exporter, - Mfa + Mfa, + Checkbox }, computed: { user () { @@ -303,6 +309,22 @@ const UserSettings = { } }) }, + changeEmail () { + const params = { + email: this.newEmail, + password: this.changeEmailPassword + } + this.$store.state.api.backendInteractor.changeEmail(params) + .then((res) => { + if (res.status === 'success') { + this.changedEmail = true + this.changeEmailError = false + } else { + this.changedEmail = false + this.changeEmailError = res.error + } + }) + }, activateTab (tabName) { this.activeTab = tabName }, diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue index ef75ac52..8c18cf49 100644 --- a/src/components/user_settings/user_settings.vue +++ b/src/components/user_settings/user_settings.vue @@ -53,12 +53,9 @@ />

- - + + {{ $t('settings.lock_account_description') }} +

@@ -75,69 +72,52 @@

- - + + {{ $t('settings.no_rich_text_description') }} +

- - + + {{ $t('settings.hide_follows_description') }} +

- - + {{ $t('settings.hide_follows_count_description') }} +

- - + {{ $t('settings.hide_followers_description') }} +

- - + {{ $t('settings.hide_followers_count_description') }} +

- - - + + + +

- - + + {{ $t('settings.discoverable') }} +

+

+ {{ $t('settings.changed_email') }} +

+ +
+

{{ $t('settings.change_password') }}

@@ -367,44 +380,24 @@ {{ $t('settings.notification_setting') }}
  • - - +
  • - - +
  • - - +
  • - - +
diff --git a/src/components/video_attachment/video_attachment.js b/src/components/video_attachment/video_attachment.js index 76b19a02..f0ca7e89 100644 --- a/src/components/video_attachment/video_attachment.js +++ b/src/components/video_attachment/video_attachment.js @@ -3,7 +3,7 @@ const VideoAttachment = { props: ['attachment', 'controls'], data () { return { - loopVideo: this.$store.state.config.loopVideo + loopVideo: this.$store.getters.mergedConfig.loopVideo } }, methods: { @@ -12,16 +12,16 @@ const VideoAttachment = { if (typeof target.webkitAudioDecodedByteCount !== 'undefined') { // non-zero if video has audio track if (target.webkitAudioDecodedByteCount > 0) { - this.loopVideo = this.loopVideo && !this.$store.state.config.loopVideoSilentOnly + this.loopVideo = this.loopVideo && !this.$store.getters.mergedConfig.loopVideoSilentOnly } } else if (typeof target.mozHasAudio !== 'undefined') { // true if video has audio track if (target.mozHasAudio) { - this.loopVideo = this.loopVideo && !this.$store.state.config.loopVideoSilentOnly + this.loopVideo = this.loopVideo && !this.$store.getters.mergedConfig.loopVideoSilentOnly } } else if (typeof target.audioTracks !== 'undefined') { if (target.audioTracks.length > 0) { - this.loopVideo = this.loopVideo && !this.$store.state.config.loopVideoSilentOnly + this.loopVideo = this.loopVideo && !this.$store.getters.mergedConfig.loopVideoSilentOnly } } } diff --git a/src/directives/body_scroll_lock.js b/src/directives/body_scroll_lock.js index 6ab20c3f..13a6de1c 100644 --- a/src/directives/body_scroll_lock.js +++ b/src/directives/body_scroll_lock.js @@ -2,42 +2,49 @@ import * as bodyScrollLock from 'body-scroll-lock' let previousNavPaddingRight let previousAppBgWrapperRight +const lockerEls = new Set([]) const disableBodyScroll = (el) => { const scrollBarGap = window.innerWidth - document.documentElement.clientWidth bodyScrollLock.disableBodyScroll(el, { reserveScrollBarGap: true }) + lockerEls.add(el) setTimeout(() => { - // If previousNavPaddingRight is already set, don't set it again. - if (previousNavPaddingRight === undefined) { - const navEl = document.getElementById('nav') - previousNavPaddingRight = window.getComputedStyle(navEl).getPropertyValue('padding-right') - navEl.style.paddingRight = previousNavPaddingRight ? `calc(${previousNavPaddingRight} + ${scrollBarGap}px)` : `${scrollBarGap}px` + if (lockerEls.size <= 1) { + // If previousNavPaddingRight is already set, don't set it again. + if (previousNavPaddingRight === undefined) { + const navEl = document.getElementById('nav') + previousNavPaddingRight = window.getComputedStyle(navEl).getPropertyValue('padding-right') + navEl.style.paddingRight = previousNavPaddingRight ? `calc(${previousNavPaddingRight} + ${scrollBarGap}px)` : `${scrollBarGap}px` + } + // If previousAppBgWrapeprRight is already set, don't set it again. + if (previousAppBgWrapperRight === undefined) { + const appBgWrapperEl = document.getElementById('app_bg_wrapper') + previousAppBgWrapperRight = window.getComputedStyle(appBgWrapperEl).getPropertyValue('right') + appBgWrapperEl.style.right = previousAppBgWrapperRight ? `calc(${previousAppBgWrapperRight} + ${scrollBarGap}px)` : `${scrollBarGap}px` + } + document.body.classList.add('scroll-locked') } - // If previousAppBgWrapeprRight is already set, don't set it again. - if (previousAppBgWrapperRight === undefined) { - const appBgWrapperEl = document.getElementById('app_bg_wrapper') - previousAppBgWrapperRight = window.getComputedStyle(appBgWrapperEl).getPropertyValue('right') - appBgWrapperEl.style.right = previousAppBgWrapperRight ? `calc(${previousAppBgWrapperRight} + ${scrollBarGap}px)` : `${scrollBarGap}px` - } - document.body.classList.add('scroll-locked') }) } const enableBodyScroll = (el) => { + lockerEls.delete(el) setTimeout(() => { - if (previousNavPaddingRight !== undefined) { - document.getElementById('nav').style.paddingRight = previousNavPaddingRight - // Restore previousNavPaddingRight to undefined so disableBodyScroll knows it can be set again. - previousNavPaddingRight = undefined + if (lockerEls.size === 0) { + if (previousNavPaddingRight !== undefined) { + document.getElementById('nav').style.paddingRight = previousNavPaddingRight + // Restore previousNavPaddingRight to undefined so disableBodyScroll knows it can be set again. + previousNavPaddingRight = undefined + } + if (previousAppBgWrapperRight !== undefined) { + document.getElementById('app_bg_wrapper').style.right = previousAppBgWrapperRight + // Restore previousAppBgWrapperRight to undefined so disableBodyScroll knows it can be set again. + previousAppBgWrapperRight = undefined + } + document.body.classList.remove('scroll-locked') } - if (previousAppBgWrapperRight !== undefined) { - document.getElementById('app_bg_wrapper').style.right = previousAppBgWrapperRight - // Restore previousAppBgWrapperRight to undefined so disableBodyScroll knows it can be set again. - previousAppBgWrapperRight = undefined - } - document.body.classList.remove('scroll-locked') }) bodyScrollLock.enableBodyScroll(el) } diff --git a/src/i18n/de.json b/src/i18n/de.json index fa9db16c..a4b4c16f 100644 --- a/src/i18n/de.json +++ b/src/i18n/de.json @@ -5,11 +5,11 @@ "features_panel": { "chat": "Chat", "gopher": "Gopher", - "media_proxy": "Media Proxy", + "media_proxy": "Medienproxy", "scope_options": "Reichweitenoptionen", "text_limit": "Textlimit", "title": "Features", - "who_to_follow": "Who to follow" + "who_to_follow": "Wem folgen?" }, "finder": { "error_fetching_user": "Fehler beim Suchen des Benutzers", @@ -29,15 +29,18 @@ "username": "Benutzername" }, "nav": { + "about": "Über", "back": "Zurück", "chat": "Lokaler Chat", "friend_requests": "Followanfragen", "mentions": "Erwähnungen", + "interactions": "Interaktionen", "dms": "Direktnachrichten", "public_tl": "Öffentliche Zeitleiste", "timeline": "Zeitleiste", "twkn": "Das gesamte bekannte Netzwerk", "user_search": "Benutzersuche", + "search": "Suche", "preferences": "Voreinstellungen" }, "notifications": { @@ -115,6 +118,9 @@ "delete_account_description": "Lösche deinen Account und alle deine Nachrichten unwiderruflich.", "delete_account_error": "Es ist ein Fehler beim Löschen deines Accounts aufgetreten. Tritt dies weiterhin auf, wende dich an den Administrator der Instanz.", "delete_account_instructions": "Tippe dein Passwort unten in das Feld ein, um die Löschung deines Accounts zu bestätigen.", + "discoverable": "Erlaubnis für automatisches Suchen nach diesem Account", + "avatar_size_instruction": "Die empfohlene minimale Größe für Avatare ist 150x150 Pixel.", + "pad_emoji": "Emojis mit Leerzeichen umrahmen", "export_theme": "Farbschema speichern", "filtering": "Filtern", "filtering_explanation": "Alle Beiträge die diese Wörter enthalten werden ausgeblendet. Ein Wort pro Zeile.", @@ -128,8 +134,11 @@ "general": "Allgemein", "hide_attachments_in_convo": "Anhänge in Unterhaltungen ausblenden", "hide_attachments_in_tl": "Anhänge in der Zeitleiste ausblenden", + "hide_muted_posts": "Verberge Beiträge stummgeschalteter Nutzer", + "max_thumbnails": "Maximale Anzahl von Vorschaubildern pro Beitrag", "hide_isp": "Instanz-spezifisches Panel ausblenden", "preload_images": "Bilder vorausladen", + "use_one_click_nsfw": "Heikle Anhänge mit nur einem Klick öffnen", "hide_post_stats": "Beitragsstatistiken verbergen (z.B. die Anzahl der Favoriten)", "hide_user_stats": "Benutzerstatistiken verbergen (z.B. die Anzahl der Follower)", "hide_filtered_statuses": "Gefilterte Beiträge verbergen", @@ -147,6 +156,9 @@ "lock_account_description": "Sperre deinen Account, um neue Follower zu genehmigen oder abzulehnen", "loop_video": "Videos wiederholen", "loop_video_silent_only": "Nur Videos ohne Ton wiederholen (z.B. Mastodons \"gifs\")", + "mutes_tab": "Mutes", + "play_videos_in_modal": "Videos in größerem Medienfenster abspielen", + "use_contain_fit": "Vorschaubilder nicht zuschneiden", "name": "Name", "name_bio": "Name & Bio", "new_password": "Neues Passwort", @@ -158,6 +170,8 @@ "no_rich_text_description": "Rich-Text Formatierungen von allen Beiträgen entfernen", "hide_follows_description": "Zeige nicht, wem ich folge", "hide_followers_description": "Zeige nicht, wer mir folgt", + "hide_follows_count_description": "Verberge die Anzahl deiner Gefolgten", + "hide_followers_count_description": "Verberge die Anzahl deiner Folgenden", "nsfw_clickthrough": "Aktiviere ausblendbares Overlay für Anhänge, die als NSFW markiert sind", "oauth_tokens": "OAuth-Token", "token": "Zeichen", @@ -176,10 +190,12 @@ "reply_visibility_all": "Alle Antworten zeigen", "reply_visibility_following": "Zeige nur Antworten an mich oder an Benutzer, denen ich folge", "reply_visibility_self": "Nur Antworten an mich anzeigen", + "autohide_floating_post_button": "Automatisches Verbergen des Knopfs für neue Beiträge (mobil)", "saving_err": "Fehler beim Speichern der Einstellungen", "saving_ok": "Einstellungen gespeichert", "security_tab": "Sicherheit", "scope_copy": "Reichweite beim Antworten übernehmen (Direktnachrichten werden immer kopiert)", + "minimal_scopes_mode": "Minimiere Reichweitenoptionen", "set_new_avatar": "Setze einen neuen Avatar", "set_new_profile_background": "Setze einen neuen Hintergrund für dein Profil", "set_new_profile_banner": "Setze einen neuen Banner für dein Profil", @@ -189,7 +205,8 @@ "subject_line_email": "Wie Email: \"re: Betreff\"", "subject_line_mastodon": "Wie Mastodon: unverändert kopieren", "subject_line_noop": "Nicht kopieren", - "stop_gifs": "Play-on-hover GIFs", + "post_status_content_type": "Beitragsart", + "stop_gifs": "Animationen nur beim Darüberfahren abspielen", "streaming": "Aktiviere automatisches Laden (Streaming) von neuen Beiträgen", "text": "Text", "theme": "Farbschema", @@ -372,5 +389,25 @@ "GiB": "GiB", "TiB": "TiB" } + }, + "search": { + "people": "Leute", + "hashtags": "Hashtags", + "person_talking": "{count} Person spricht darüber", + "people_talking": "{count} Leute sprechen darüber", + "no_results": "Keine Ergebnisse" + }, + "password_reset": { + "forgot_password": "Passwort vergessen?", + "password_reset": "Password zurücksetzen", + "instruction": "Wenn du hier deinen Benutznamen oder die zugehörige E-Mail-Adresse eingibst, kann dir der Server einen Link zum Passwortzurücksetzen zuschicken.", + "placeholder": "Dein Benutzername oder die zugehörige E-Mail-Adresse", + "check_email": "Im E-Mail-Posteingang des angebenen Kontos müsste sich jetzt (oder zumindest in Kürze) die E-Mail mit dem Link zum Passwortzurücksetzen befinden.", + "return_home": "Zurück zur Heimseite", + "not_found": "Benutzername/E-Mail-Adresse nicht gefunden. Vertippt?", + "too_many_requests": "Kurze Pause. Zu viele Versuche. Bitte, später nochmal probieren.", + "password_reset_disabled": "Passwortzurücksetzen deaktiviert. Bitte Administrator kontaktieren.", + "password_reset_required": "Passwortzurücksetzen erforderlich", + "password_reset_required_but_mailer_is_disabled": "Passwortzurücksetzen wäre erforderlich, ist aber deaktiviert. Bitte Administrator kontaktieren." } } diff --git a/src/i18n/en.json b/src/i18n/en.json index c6c98c48..8ecb3f3d 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -219,6 +219,9 @@ "cGreen": "Green (Retweet)", "cOrange": "Orange (Favorite)", "cRed": "Red (Cancel)", + "change_email": "Change Email", + "change_email_error": "There was an issue changing your email.", + "changed_email": "Email changed successfully!", "change_password": "Change Password", "change_password_error": "There was an issue changing your password.", "changed_password": "Password changed successfully!", @@ -277,6 +280,7 @@ "use_contain_fit": "Don't crop the attachment in thumbnails", "name": "Name", "name_bio": "Name & Bio", + "new_email": "New Email", "new_password": "New password", "notification_visibility": "Types of notifications to show", "notification_visibility_follows": "Follows", @@ -558,6 +562,8 @@ "unmute": "Unmute", "unmute_progress": "Unmuting...", "mute_progress": "Muting...", + "hide_repeats": "Hide repeats", + "show_repeats": "Show repeats", "admin_menu": { "moderation": "Moderation", "grant_admin": "Grant Admin", @@ -633,6 +639,8 @@ "return_home": "Return to the home page", "not_found": "We couldn't find that email or username.", "too_many_requests": "You have reached the limit of attempts, try again later.", - "password_reset_disabled": "Password reset is disabled. Please contact your instance administrator." + "password_reset_disabled": "Password reset is disabled. Please contact your instance administrator.", + "password_reset_required": "You must reset your password to log in.", + "password_reset_required_but_mailer_is_disabled": "You must reset your password, but password reset is disabled. Please contact your instance administrator." } } diff --git a/src/i18n/es.json b/src/i18n/es.json index 91c7f383..163eb707 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -45,8 +45,8 @@ "error": "Se ha producido un error al importar el archivo." }, "login": { - "login": "Identificación", - "description": "Identificación con OAuth", + "login": "Identificarse", + "description": "Identificarse con OAuth", "logout": "Cerrar sesión", "password": "Contraseña", "placeholder": "p.ej. lain", @@ -68,6 +68,7 @@ }, "nav": { "about": "Acerca de", + "administration": "Administración", "back": "Volver", "chat": "Chat Local", "friend_requests": "Solicitudes de seguimiento", @@ -106,6 +107,15 @@ "expired": "La encuesta terminó hace {0}", "not_enough_options": "Muy pocas opciones únicas en la encuesta" }, + "emoji": { + "stickers": "Pegatinas", + "emoji": "Emoji", + "keep_open": "Mantener el selector abierto", + "search_emoji": "Buscar un emoji", + "add_emoji": "Insertar un emoji", + "custom": "Emojis personalizados", + "unicode": "Emojis unicode" + }, "stickers": { "add_sticker": "Añadir Pegatina" }, @@ -222,7 +232,9 @@ "data_import_export_tab": "Importar / Exportar Datos", "default_vis": "Alcance de visibilidad por defecto", "delete_account": "Eliminar la cuenta", + "discoverable": "Permitir la aparición de esta cuenta en los resultados de búsqueda y otros servicios", "delete_account_description": "Eliminar para siempre la cuenta y todos los mensajes.", + "pad_emoji": "Rellenar con espacios al agregar emojis desde el selector", "delete_account_error": "Hubo un error al eliminar tu cuenta. Si el fallo persiste, ponte en contacto con el administrador de tu instancia.", "delete_account_instructions": "Escribe tu contraseña para confirmar la eliminación de tu cuenta.", "avatar_size_instruction": "El tamaño mínimo recomendado para el avatar es de 150X150 píxeles.", @@ -277,6 +289,8 @@ "no_mutes": "No hay usuarios sinlenciados", "hide_follows_description": "No mostrar a quién sigo", "hide_followers_description": "No mostrar quién me sigue", + "hide_follows_count_description": "No mostrar el número de cuentas que sigo", + "hide_followers_count_description": "No mostrar el número de cuentas que me siguen", "show_admin_badge": "Mostrar la insignia de Administrador en mi perfil", "show_moderator_badge": "Mostrar la insignia de Moderador en mi perfil", "nsfw_clickthrough": "Activar el clic para ocultar los adjuntos NSFW", @@ -529,6 +543,7 @@ "follows_you": "¡Te sigue!", "its_you": "¡Eres tú!", "media": "Media", + "mention": "Mencionar", "mute": "Silenciar", "muted": "Silenciado", "per_day": "por día", diff --git a/src/i18n/eu.json b/src/i18n/eu.json index ad8f4c05..1c75bf75 100644 --- a/src/i18n/eu.json +++ b/src/i18n/eu.json @@ -68,6 +68,7 @@ }, "nav": { "about": "Honi buruz", + "administration": "Administrazioa", "back": "Atzera", "chat": "Txat lokala", "friend_requests": "Jarraitzeko eskaerak", @@ -106,6 +107,15 @@ "expired": "Inkesta {0} bukatu zen", "not_enough_options": "Aukera gutxiegi inkestan" }, + "emoji": { + "stickers": "Pegatinak", + "emoji": "Emoji", + "keep_open": "Mantendu hautatzailea zabalik", + "search_emoji": "Bilatu emoji bat", + "add_emoji": "Emoji bat gehitu", + "custom": "Ohiko emojiak", + "unicode": "Unicode emojiak" + }, "stickers": { "add_sticker": "Pegatina gehitu" }, @@ -199,12 +209,12 @@ "avatarRadius": "Avatarrak", "background": "Atzeko planoa", "bio": "Biografia", - "block_export": "Bloke esportatzea", - "block_export_button": "Esportatu zure blokeak csv fitxategi batera", - "block_import": "Bloke inportazioa", - "block_import_error": "Errorea blokeak inportatzen", - "blocks_imported": "Blokeak inportaturik! Hauek prozesatzeak denbora hartuko du.", - "blocks_tab": "Blokeak", + "block_export": "Blokeatu dituzunak esportatu", + "block_export_button": "Esportatu blokeatutakoak csv fitxategi batera", + "block_import": "Blokeatu dituzunak inportatu", + "block_import_error": "Errorea blokeatutakoak inportatzen", + "blocks_imported": "Blokeatutakoak inportaturik! Hauek prozesatzeak denbora hartuko du.", + "blocks_tab": "Blokeatutakoak", "btnRadius": "Botoiak", "cBlue": "Urdina (erantzun, jarraitu)", "cGreen": "Berdea (Bertxiotu)", @@ -222,7 +232,9 @@ "data_import_export_tab": "Datuak Inportatu / Esportatu", "default_vis": "Lehenetsitako ikusgaitasunak", "delete_account": "Ezabatu kontua", + "discoverable": "Baimendu zure kontua kanpo bilaketa-emaitzetan eta bestelako zerbitzuetan agertzea", "delete_account_description": "Betirako ezabatu zure kontua eta zure mezu guztiak", + "pad_emoji": "Zuriuneak gehitu emoji bat aukeratzen denean", "delete_account_error": "Arazo bat gertatu da zure kontua ezabatzerakoan. Arazoa jarraitu eskero, administratzailearekin harremanetan jarri.", "delete_account_instructions": "Idatzi zure pasahitza kontua ezabatzeko.", "avatar_size_instruction": "Avatar irudien gomendatutako gutxieneko tamaina 150x150 pixel dira.", @@ -254,7 +266,7 @@ "instance_default": "(lehenetsia: {value})", "instance_default_simple": "(lehenetsia)", "interface": "Interfazea", - "interfaceLanguage": "Interfaze hizkuntza", + "interfaceLanguage": "Interfazearen hizkuntza", "invalid_theme_imported": "Hautatutako fitxategia ez da onartutako Pleroma gaia. Ez da zure gaian aldaketarik burutu.", "limited_availability": "Ez dago erabilgarri zure nabigatzailean", "links": "Estekak", @@ -277,6 +289,8 @@ "no_mutes": "Ez daude erabiltzaile mututuak", "hide_follows_description": "Ez erakutsi nor jarraitzen ari naizen", "hide_followers_description": "Ez erakutsi nor ari den ni jarraitzen", + "hide_follows_count_description": "Ez erakutsi jarraitzen ari naizen kontuen kopurua", + "hide_followers_count_description": "Ez erakutsi nire jarraitzaileen kontuen kopurua", "show_admin_badge": "Erakutsi Administratzaile etiketa nire profilan", "show_moderator_badge": "Erakutsi Moderatzaile etiketa nire profilan", "nsfw_clickthrough": "Gaitu klika hunkigarri eranskinak ezkutatzeko", @@ -449,7 +463,7 @@ }, "version": { "title": "Bertsioa", - "backend_version": "Backend Bertsio", + "backend_version": "Backend Bertsioa", "frontend_version": "Frontend Bertsioa" } }, @@ -529,6 +543,7 @@ "follows_you": "Jarraitzen dizu!", "its_you": "Zu zara!", "media": "Multimedia", + "mention": "Aipatu", "mute": "Isilarazi", "muted": "Isilduta", "per_day": "eguneko", @@ -543,6 +558,8 @@ "unmute": "Isiltasuna kendu", "unmute_progress": "Isiltasuna kentzen...", "mute_progress": "Isiltzen...", + "hide_repeats": "Ezkutatu errepikapenak", + "show_repeats": "Erakutsi errpekiapenak", "admin_menu": { "moderation": "Moderazioa", "grant_admin": "Administratzaile baimena", @@ -618,6 +635,8 @@ "return_home": "Itzuli hasierara", "not_found": "Ezin izan dugu helbide elektroniko edo erabiltzaile hori aurkitu.", "too_many_requests": "Saiakera gehiegi burutu ditzu, saiatu berriro geroxeago.", - "password_reset_disabled": "Pasahitza berrezartzea debekatuta dago. Mesedez, jarri harremanetan instantzia administratzailearekin." + "password_reset_disabled": "Pasahitza berrezartzea debekatuta dago. Mesedez, jarri harremanetan instantzia administratzailearekin.", + "password_reset_required": "Pasahitza berrezarri behar duzu saioa hasteko.", + "password_reset_required_but_mailer_is_disabled": "Pasahitza berrezarri behar duzu, baina pasahitza berrezartzeko aukera desgaituta dago. Mesedez, jarri harremanetan instantziaren administratzailearekin." } } \ No newline at end of file diff --git a/src/i18n/ru.json b/src/i18n/ru.json index 16268425..f8bcd996 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -127,6 +127,9 @@ "cGreen": "Повторить", "cOrange": "Нравится", "cRed": "Отменить", + "change_email": "Сменить email", + "change_email_error": "Произошла ошибка при попытке изменить email.", + "changed_email": "Email изменён успешно.", "change_password": "Сменить пароль", "change_password_error": "Произошла ошибка при попытке изменить пароль.", "changed_password": "Пароль изменён успешно.", @@ -169,6 +172,7 @@ "loop_video_silent_only": "Зацикливать только беззвучные видео (т.е. \"гифки\" с Mastodon)", "name": "Имя", "name_bio": "Имя и описание", + "new_email": "Новый email", "new_password": "Новый пароль", "notification_visibility": "Показывать уведомления", "notification_visibility_follows": "Подписки", diff --git a/src/main.js b/src/main.js index 7923ffe8..a9db1cff 100644 --- a/src/main.js +++ b/src/main.js @@ -41,7 +41,13 @@ Vue.use(VueChatScroll) Vue.use(VueClickOutside) Vue.use(PortalVue) Vue.use(VBodyScrollLock) -Vue.use(VTooltip) +Vue.use(VTooltip, { + popover: { + defaultTrigger: 'hover click', + defaultContainer: false, + defaultOffset: 5 + } +}) const i18n = new VueI18n({ // By default, use the browser locale, we will update it if neccessary diff --git a/src/modules/config.js b/src/modules/config.js index cf04d14f..78314118 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -3,8 +3,9 @@ import { setPreset, applyTheme } from '../services/style_setter/style_setter.js' const browserLocale = (window.navigator.language || 'en').split('-')[0] -const defaultState = { +export const defaultState = { colors: {}, + // bad name: actually hides posts of muted USERS hideMutedPosts: undefined, // instance default collapseMessageWithSubject: undefined, // instance default padEmoji: true, @@ -37,11 +38,37 @@ const defaultState = { subjectLineBehavior: undefined, // instance default alwaysShowSubjectInput: undefined, // instance default postContentType: undefined, // instance default - minimalScopesMode: undefined // instance default + minimalScopesMode: undefined, // instance default + // This hides statuses filtered via a word filter + hideFilteredStatuses: undefined, // instance default + playVideosInModal: false, + useOneClickNsfw: false, + useContainFit: false, + hidePostStats: undefined, // instance default + hideUserStats: undefined // instance default } +// caching the instance default properties +export const instanceDefaultProperties = Object.entries(defaultState) + .filter(([key, value]) => value === undefined) + .map(([key, value]) => key) + const config = { state: defaultState, + getters: { + mergedConfig (state, getters, rootState, rootGetters) { + const { instance } = rootState + return { + ...state, + ...instanceDefaultProperties + .map(key => [key, state[key] === undefined + ? instance[key] + : state[key] + ]) + .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}) + } + } + }, mutations: { setOption (state, { name, value }) { set(state, name, value) diff --git a/src/modules/instance.js b/src/modules/instance.js index 15280e82..7b0e0da4 100644 --- a/src/modules/instance.js +++ b/src/modules/instance.js @@ -1,5 +1,6 @@ import { set } from 'vue' import { setPreset } from '../services/style_setter/style_setter.js' +import { instanceDefaultProperties } from './config.js' const defaultState = { // Stuff from static/config.json and apiConfig @@ -74,6 +75,13 @@ const instance = { } } }, + getters: { + instanceDefaultConfig (state) { + return instanceDefaultProperties + .map(key => [key, state[key]]) + .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}) + } + }, actions: { setInstanceOption ({ commit, dispatch }, { name, value }) { commit('setInstanceOption', { name, value }) diff --git a/src/modules/statuses.js b/src/modules/statuses.js index 918065d2..f11ffdcd 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -537,6 +537,10 @@ const statuses = { setNotificationsSilence ({ rootState, commit }, { value }) { commit('setNotificationsSilence', { value }) }, + fetchStatus ({ rootState, dispatch }, id) { + rootState.api.backendInteractor.fetchStatus({ id }) + .then((status) => dispatch('addNewStatuses', { statuses: [status] })) + }, deleteStatus ({ rootState, commit }, status) { commit('setDeleted', { status }) apiService.deleteStatus({ id: status.id, credentials: rootState.users.currentUser.credentials }) diff --git a/src/modules/users.js b/src/modules/users.js index 0b2f8a9e..1c9ff5e8 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -60,6 +60,18 @@ const unmuteUser = (store, id) => { .then((relationship) => store.commit('updateUserRelationship', [relationship])) } +const hideReblogs = (store, userId) => { + return store.rootState.api.backendInteractor.followUser({ id: userId, reblogs: false }) + .then((relationship) => { + store.commit('updateUserRelationship', [relationship]) + }) +} + +const showReblogs = (store, userId) => { + return store.rootState.api.backendInteractor.followUser({ id: userId, reblogs: true }) + .then((relationship) => store.commit('updateUserRelationship', [relationship])) +} + export const mutations = { setMuted (state, { user: { id }, muted }) { const user = state.usersObject[id] @@ -135,6 +147,7 @@ export const mutations = { user.muted = relationship.muting user.statusnet_blocking = relationship.blocking user.subscribed = relationship.subscribing + user.showing_reblogs = relationship.showing_reblogs } }) }, @@ -272,6 +285,12 @@ const users = { unmuteUser (store, id) { return unmuteUser(store, id) }, + hideReblogs (store, id) { + return hideReblogs(store, id) + }, + showReblogs (store, id) { + return showReblogs(store, id) + }, muteUsers (store, ids = []) { return Promise.all(ids.map(id => muteUser(store, id))) }, diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 887d7d7a..8f5eb416 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -8,6 +8,7 @@ const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications const BLOCKS_IMPORT_URL = '/api/pleroma/blocks_import' const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import' const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account' +const CHANGE_EMAIL_URL = '/api/pleroma/change_email' const CHANGE_PASSWORD_URL = '/api/pleroma/change_password' const TAG_USER_URL = '/api/pleroma/admin/users/tag' const PERMISSION_GROUP_URL = (screenName, right) => `/api/pleroma/admin/users/${screenName}/permission_group/${right}` @@ -16,12 +17,12 @@ const ADMIN_USERS_URL = '/api/pleroma/admin/users' const SUGGESTIONS_URL = '/api/v1/suggestions' const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings' -const MFA_SETTINGS_URL = '/api/pleroma/profile/mfa' -const MFA_BACKUP_CODES_URL = '/api/pleroma/profile/mfa/backup_codes' +const MFA_SETTINGS_URL = '/api/pleroma/accounts/mfa' +const MFA_BACKUP_CODES_URL = '/api/pleroma/accounts/mfa/backup_codes' -const MFA_SETUP_OTP_URL = '/api/pleroma/profile/mfa/setup/totp' -const MFA_CONFIRM_OTP_URL = '/api/pleroma/profile/mfa/confirm/totp' -const MFA_DISABLE_OTP_URL = '/api/pleroma/profile/mfa/totp' +const MFA_SETUP_OTP_URL = '/api/pleroma/accounts/mfa/setup/totp' +const MFA_CONFIRM_OTP_URL = '/api/pleroma/accounts/mfa/confirm/totp' +const MFA_DISABLE_OTP_URL = '/api/pleroma/account/mfa/totp' const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials' const MASTODON_REGISTRATION_URL = '/api/v1/accounts' @@ -219,10 +220,16 @@ const authHeaders = (accessToken) => { } } -const followUser = ({ id, credentials }) => { +const followUser = ({ id, credentials, ...options }) => { let url = MASTODON_FOLLOW_URL(id) + const form = {} + if (options.reblogs !== undefined) { form['reblogs'] = options.reblogs } return fetch(url, { - headers: authHeaders(credentials), + body: JSON.stringify(form), + headers: { + ...authHeaders(credentials), + 'Content-Type': 'application/json' + }, method: 'POST' }).then((data) => data.json()) } @@ -685,6 +692,20 @@ const deleteAccount = ({ credentials, password }) => { .then((response) => response.json()) } +const changeEmail = ({ credentials, email, password }) => { + const form = new FormData() + + form.append('email', email) + form.append('password', password) + + return fetch(CHANGE_EMAIL_URL, { + body: form, + method: 'POST', + headers: authHeaders(credentials) + }) + .then((response) => response.json()) +} + const changePassword = ({ credentials, password, newPassword, newPasswordConfirmation }) => { const form = new FormData() @@ -960,6 +981,7 @@ const apiService = { importBlocks, importFollows, deleteAccount, + changeEmail, changePassword, settingsMFA, mfaDisableOTP, diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js index 3c44a10c..d6617276 100644 --- a/src/services/backend_interactor_service/backend_interactor_service.js +++ b/src/services/backend_interactor_service/backend_interactor_service.js @@ -31,8 +31,8 @@ const backendInteractorService = credentials => { return apiService.fetchUserRelationship({ id, credentials }) } - const followUser = (id) => { - return apiService.followUser({ credentials, id }) + const followUser = ({ id, reblogs }) => { + return apiService.followUser({ credentials, id, reblogs }) } const unfollowUser = (id) => { @@ -131,6 +131,7 @@ const backendInteractorService = credentials => { const importFollows = (file) => apiService.importFollows({ file, credentials }) const deleteAccount = ({ password }) => apiService.deleteAccount({ credentials, password }) + const changeEmail = ({ email, password }) => apiService.changeEmail({ credentials, email, password }) const changePassword = ({ password, newPassword, newPasswordConfirmation }) => apiService.changePassword({ credentials, password, newPassword, newPasswordConfirmation }) @@ -195,6 +196,7 @@ const backendInteractorService = credentials => { importBlocks, importFollows, deleteAccount, + changeEmail, changePassword, fetchSettingsMFA, generateMfaBackupCodes, diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index 67664af8..5f45660d 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -69,6 +69,7 @@ export const parseUser = (data) => { output.following = relationship.following output.statusnet_blocking = relationship.blocking output.muted = relationship.muting + output.showing_reblogs = relationship.showing_reblogs output.subscribed = relationship.subscribing } diff --git a/src/services/follow_manipulate/follow_manipulate.js b/src/services/follow_manipulate/follow_manipulate.js index d82ce593..598cb5f7 100644 --- a/src/services/follow_manipulate/follow_manipulate.js +++ b/src/services/follow_manipulate/follow_manipulate.js @@ -14,7 +14,7 @@ const fetchUser = (attempt, user, store) => new Promise((resolve, reject) => { }) export const requestFollow = (user, store) => new Promise((resolve, reject) => { - store.state.api.backendInteractor.followUser(user.id) + store.state.api.backendInteractor.followUser({ id: user.id }) .then((updated) => { store.commit('updateUserRelationship', [updated]) diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js index b6c4cf80..47008026 100644 --- a/src/services/notifications_fetcher/notifications_fetcher.service.js +++ b/src/services/notifications_fetcher/notifications_fetcher.service.js @@ -8,11 +8,10 @@ const update = ({ store, notifications, older }) => { const fetchAndUpdate = ({ store, credentials, older = false }) => { const args = { credentials } + const { getters } = store const rootState = store.rootState || store.state const timelineData = rootState.statuses.notifications - const hideMutedPosts = typeof rootState.config.hideMutedPosts === 'undefined' - ? rootState.instance.hideMutedPosts - : rootState.config.hideMutedPosts + const hideMutedPosts = getters.mergedConfig.hideMutedPosts args['withMuted'] = !hideMutedPosts diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js index f72688f8..9eb30c2d 100644 --- a/src/services/timeline_fetcher/timeline_fetcher.service.js +++ b/src/services/timeline_fetcher/timeline_fetcher.service.js @@ -15,13 +15,21 @@ const update = ({ store, statuses, timeline, showImmediately, userId }) => { }) } -const fetchAndUpdate = ({ store, credentials, timeline = 'friends', older = false, showImmediately = false, userId = false, tag = false, until }) => { +const fetchAndUpdate = ({ + store, + credentials, + timeline = 'friends', + older = false, + showImmediately = false, + userId = false, + tag = false, + until +}) => { const args = { timeline, credentials } const rootState = store.rootState || store.state + const { getters } = store const timelineData = rootState.statuses.timelines[camelCase(timeline)] - const hideMutedPosts = typeof rootState.config.hideMutedPosts === 'undefined' - ? rootState.instance.hideMutedPosts - : rootState.config.hideMutedPosts + const hideMutedPosts = getters.mergedConfig.hideMutedPosts if (older) { args['until'] = until || timelineData.minId diff --git a/static/styles.json b/static/styles.json index 00ad6ae1..842092c4 100644 --- a/static/styles.json +++ b/static/styles.json @@ -5,11 +5,11 @@ "bird": [ "Bird", "#f8fafd", "#e6ecf0", "#14171a", "#0084b8", "#e0245e", "#17bf63", "#1b95e0", "#fab81e"], "ir-black": [ "Ir Black", "#000000", "#242422", "#b5b3aa", "#ff6c60", "#FF6C60", "#A8FF60", "#96CBFE", "#FFFFB6" ], "monokai": [ "Monokai", "#272822", "#383830", "#f8f8f2", "#f92672", "#F92672", "#a6e22e", "#66d9ef", "#f4bf75" ], - "mammal": [ "Mammal", "#272c37", "#444b5d", "#f8f8f8", "#9bacc8", "#7f3142", "#2bd850", "#2b90d9", "#ca8f04" ], "redmond-xx": "/static/themes/redmond-xx.json", "redmond-xx-se": "/static/themes/redmond-xx-se.json", "redmond-xxi": "/static/themes/redmond-xxi.json", "breezy-dark": "/static/themes/breezy-dark.json", - "breezy-light": "/static/themes/breezy-light.json" + "breezy-light": "/static/themes/breezy-light.json", + "mammal": "/static/themes/mammal.json" } diff --git a/static/themes/mammal.json b/static/themes/mammal.json new file mode 100644 index 00000000..50b8e2f0 --- /dev/null +++ b/static/themes/mammal.json @@ -0,0 +1,57 @@ +{ + "_pleroma_theme_version": 2, + "name": "Mammal", + "theme": { + "shadows": { + "button": [], + "buttonHover": [ + { + "x": "0", + "y": "0", + "blur": "0", + "spread": 1024, + "color": "#56a7e1", + "alpha": "1", + "inset": true + } + ], + "buttonPressed": [ + { + "x": "0", + "y": "0", + "blur": "0", + "spread": 1024, + "color": "#56a7e1", + "alpha": "1", + "inset": true + } + ], + "panel": [], + "panelHeader": [], + "topBar": [] + }, + "opacity": { "input": "1" }, + "colors": { + "bg": "#282c37", + "text": "#f8f8f8", + "link": "#9bacc8", + "fg": "#444b5d", + "input": "#FFFFFF", + "inputText": "#282c37", + "btn": "#2b90d9", + "btnText": "#FFFFFF", + "cRed": "#7f3142", + "cBlue": "#2b90d9", + "cGreen": "#2bd850", + "cOrange": "#ca8f04" + }, + "radii": { + "btn": 4, + "input": 4, + "panel": "0", + "avatar": "4", + "avatarAlt": "4", + "attachment": "4" + } + } +} diff --git a/test/unit/specs/components/emoji_input.spec.js b/test/unit/specs/components/emoji_input.spec.js index 368d623d..b1b98802 100644 --- a/test/unit/specs/components/emoji_input.spec.js +++ b/test/unit/specs/components/emoji_input.spec.js @@ -12,8 +12,8 @@ const generateInput = (value, padEmoji = true) => { }, mocks: { $store: { - state: { - config: { + getters: { + mergedConfig: { padEmoji } } diff --git a/test/unit/specs/components/user_profile.spec.js b/test/unit/specs/components/user_profile.spec.js index 6de9491a..1b6a47d7 100644 --- a/test/unit/specs/components/user_profile.spec.js +++ b/test/unit/specs/components/user_profile.spec.js @@ -18,7 +18,14 @@ const actions = { } const testGetters = { - findUser: state => getters.findUser(state.users) + findUser: state => getters.findUser(state.users), + mergedConfig: state => ({ + colors: '', + highlight: {}, + customTheme: { + colors: [] + } + }) } const localUser = { @@ -45,13 +52,6 @@ const externalProfileStore = new Vuex.Store({ interface: { browserSupport: '' }, - config: { - colors: '', - highlight: {}, - customTheme: { - colors: [] - } - }, instance: { hideUserStats: true },