diff --git a/CHANGELOG.md b/CHANGELOG.md index d08da09e5..0d6ef1a5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,16 @@ # 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 +- User profile button clutter organized into a menu - Emoji picker - Started changelog anew +- Ability to change user's email +- About page ### 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/boot/after_store.js b/src/boot/after_store.js index 80a558497..226b67d8a 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -184,6 +184,15 @@ const getAppSecret = async ({ store }) => { }) } +const resolveStaffAccounts = async ({ store, accounts }) => { + const backendInteractor = store.state.api.backendInteractor + let nicknames = accounts.map(uri => uri.split('/').pop()) + .map(id => backendInteractor.fetchUser({ id })) + nicknames = await Promise.all(nicknames) + + store.dispatch('setInstanceOption', { name: 'staffAccounts', value: nicknames }) +} + const getNodeInfo = async ({ store }) => { try { const res = await window.fetch('/nodeinfo/2.0.json') @@ -212,6 +221,12 @@ const getNodeInfo = async ({ store }) => { const frontendVersion = window.___pleromafe_commit_hash store.dispatch('setInstanceOption', { name: 'frontendVersion', value: frontendVersion }) store.dispatch('setInstanceOption', { name: 'tagPolicyAvailable', value: metadata.federation.mrf_policies.includes('TagPolicy') }) + + const federation = metadata.federation + store.dispatch('setInstanceOption', { name: 'federationPolicy', value: federation }) + + const accounts = metadata.staffAccounts + await resolveStaffAccounts({ store, accounts }) } else { throw (res) } diff --git a/src/boot/routes.js b/src/boot/routes.js index 5670236c7..7400a682c 100644 --- a/src/boot/routes.js +++ b/src/boot/routes.js @@ -18,6 +18,7 @@ import AuthForm from 'components/auth_form/auth_form.js' import ChatPanel from 'components/chat_panel/chat_panel.vue' import WhoToFollow from 'components/who_to_follow/who_to_follow.vue' import About from 'components/about/about.vue' +import RemoteUserResolver from 'components/remote_user_resolver/remote_user_resolver.vue' export default (store) => { const validateAuthenticatedRoute = (to, from, next) => { @@ -42,6 +43,16 @@ export default (store) => { { name: 'friends', path: '/main/friends', component: FriendsTimeline, beforeEnter: validateAuthenticatedRoute }, { name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline }, { name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } }, + { name: 'remote-user-profile-acct', + path: '/remote-users/(@?):username([^/@]+)@:hostname([^/@]+)', + component: RemoteUserResolver, + beforeEnter: validateAuthenticatedRoute + }, + { name: 'remote-user-profile', + path: '/remote-users/:hostname/:username', + component: RemoteUserResolver, + beforeEnter: validateAuthenticatedRoute + }, { name: 'external-user-profile', path: '/users/:id', component: UserProfile }, { name: 'interactions', path: '/users/:username/interactions', component: Interactions, beforeEnter: validateAuthenticatedRoute }, { name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute }, diff --git a/src/components/about/about.js b/src/components/about/about.js index ae1cb1821..1df258450 100644 --- a/src/components/about/about.js +++ b/src/components/about/about.js @@ -1,15 +1,24 @@ import InstanceSpecificPanel from '../instance_specific_panel/instance_specific_panel.vue' import FeaturesPanel from '../features_panel/features_panel.vue' import TermsOfServicePanel from '../terms_of_service_panel/terms_of_service_panel.vue' +import StaffPanel from '../staff_panel/staff_panel.vue' +import MRFTransparencyPanel from '../mrf_transparency_panel/mrf_transparency_panel.vue' const About = { components: { InstanceSpecificPanel, FeaturesPanel, - TermsOfServicePanel + TermsOfServicePanel, + StaffPanel, + MRFTransparencyPanel }, computed: { - showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel } + showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel }, + showInstanceSpecificPanel () { + return this.$store.state.instance.showInstanceSpecificPanel && + !this.$store.getters.mergedConfig.hideISP && + this.$store.state.instance.instanceSpecificPanelContent + } } } diff --git a/src/components/about/about.vue b/src/components/about/about.vue index 62ae16ea3..518f6184d 100644 --- a/src/components/about/about.vue +++ b/src/components/about/about.vue @@ -1,8 +1,10 @@ diff --git a/src/components/checkbox/checkbox.vue b/src/components/checkbox/checkbox.vue index 6e164c1bf..1113f81d8 100644 --- a/src/components/checkbox/checkbox.vue +++ b/src/components/checkbox/checkbox.vue @@ -2,7 +2,7 @@ diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js index fd54cf9d9..7740d9784 100644 --- a/src/components/emoji_picker/emoji_picker.js +++ b/src/components/emoji_picker/emoji_picker.js @@ -1,9 +1,12 @@ import Checkbox from '../checkbox/checkbox.vue' import { set } from 'vue' -const LOAD_EMOJI_BY = 50 -const LOAD_EMOJI_INTERVAL = 100 -const LOAD_EMOJI_SANE_AMOUNT = 500 +// At widest, approximately 20 emoji are visible in a row, +// loading 3 rows, could be overkill for narrow picker +const LOAD_EMOJI_BY = 60 + +// When to start loading new batch emoji, in pixels +const LOAD_EMOJI_MARGIN = 64 const filterByKeyword = (list, keyword = '') => { return list.filter(x => x.displayText.includes(keyword)) @@ -24,10 +27,8 @@ const EmojiPicker = { showingStickers: false, groupsScrolledClass: 'scrolled-top', keepOpen: false, - customEmojiBuffer: (this.$store.state.instance.customEmoji || []) - .slice(0, LOAD_EMOJI_BY), + customEmojiBufferSlice: LOAD_EMOJI_BY, customEmojiTimeout: null, - customEmojiCounter: LOAD_EMOJI_BY, customEmojiLoadAllConfirmed: false } }, @@ -36,10 +37,22 @@ const EmojiPicker = { Checkbox }, methods: { + onStickerUploaded (e) { + this.$emit('sticker-uploaded', e) + }, + onStickerUploadFailed (e) { + this.$emit('sticker-upload-failed', e) + }, onEmoji (emoji) { const value = emoji.imageUrl ? `:${emoji.displayText}:` : emoji.replacement this.$emit('emoji', { insertion: value, keepOpen: this.keepOpen }) }, + onScroll (e) { + const target = (e && e.target) || this.$refs['emoji-groups'] + this.updateScrolledClass(target) + this.scrolledGroup(target) + this.triggerLoadMore(target) + }, highlight (key) { const ref = this.$refs['group-' + key] const top = ref[0].offsetTop @@ -49,9 +62,7 @@ const EmojiPicker = { this.$refs['emoji-groups'].scrollTop = top + 1 }) }, - scrolledGroup (e) { - const target = (e && e.target) || this.$refs['emoji-groups'] - const top = target.scrollTop + 5 + updateScrolledClass (target) { if (target.scrollTop <= 5) { this.groupsScrolledClass = 'scrolled-top' } else if (target.scrollTop >= target.scrollTopMax - 5) { @@ -59,6 +70,28 @@ const EmojiPicker = { } else { this.groupsScrolledClass = 'scrolled-middle' } + }, + triggerLoadMore (target) { + const ref = this.$refs['group-end-custom'][0] + if (!ref) return + const bottom = ref.offsetTop + ref.offsetHeight + + const scrollerBottom = target.scrollTop + target.clientHeight + const scrollerTop = target.scrollTop + const scrollerMax = target.scrollHeight + + // Loads more emoji when they come into view + const approachingBottom = bottom - scrollerBottom < LOAD_EMOJI_MARGIN + // Always load when at the very top in case there's no scroll space yet + const atTop = scrollerTop < 5 + // Don't load when looking at unicode category or at the very bottom + const bottomAboveViewport = bottom < scrollerTop || scrollerBottom === scrollerMax + if (!bottomAboveViewport && (approachingBottom || atTop)) { + this.loadEmoji() + } + }, + scrolledGroup (target) { + const top = target.scrollTop + 5 this.$nextTick(() => { this.emojisView.forEach(group => { const ref = this.$refs['group-' + group.id] @@ -68,67 +101,40 @@ const EmojiPicker = { }) }) }, - loadEmojiInsane () { - this.customEmojiLoadAllConfirmed = true - this.continueEmojiLoad() - }, loadEmoji () { const allLoaded = this.customEmojiBuffer.length === this.filteredEmoji.length - const saneLoaded = this.customEmojiBuffer.length >= LOAD_EMOJI_SANE_AMOUNT && - !this.customEmojiLoadAllConfirmed - if (allLoaded || saneLoaded) { + if (allLoaded) { return } - this.customEmojiBuffer.push( - ...this.filteredEmoji.slice( - this.customEmojiCounter, - this.customEmojiCounter + LOAD_EMOJI_BY - ) - ) - this.customEmojiTimeout = window.setTimeout(this.loadEmoji, LOAD_EMOJI_INTERVAL) - this.customEmojiCounter += LOAD_EMOJI_BY + this.customEmojiBufferSlice += LOAD_EMOJI_BY }, startEmojiLoad (forceUpdate = false) { + if (!forceUpdate) { + this.keyword = '' + } + this.$nextTick(() => { + this.$refs['emoji-groups'].scrollTop = 0 + }) const bufferSize = this.customEmojiBuffer.length - const bufferPrefilledSane = bufferSize === LOAD_EMOJI_SANE_AMOUNT && !this.customEmojiLoadAllConfirmed const bufferPrefilledAll = bufferSize === this.filteredEmoji.length - if (forceUpdate || bufferPrefilledSane || bufferPrefilledAll) { + if (bufferPrefilledAll && !forceUpdate) { return } - if (this.customEmojiTimeout) { - window.clearTimeout(this.customEmojiTimeout) - } - - set( - this, - 'customEmojiBuffer', - this.filteredEmoji.slice(0, LOAD_EMOJI_BY) - ) - this.customEmojiCounter = LOAD_EMOJI_BY - this.customEmojiTimeout = window.setTimeout(this.loadEmoji, LOAD_EMOJI_INTERVAL) - }, - continueEmojiLoad () { - this.customEmojiTimeout = window.setTimeout(this.loadEmoji, LOAD_EMOJI_INTERVAL) + this.customEmojiBufferSlice = LOAD_EMOJI_BY }, toggleStickers () { this.showingStickers = !this.showingStickers }, setShowStickers (value) { this.showingStickers = value - }, - onStickerUploaded (e) { - this.$emit('sticker-uploaded', e) - }, - onStickerUploadFailed (e) { - this.$emit('sticker-upload-failed', e) } }, watch: { keyword () { this.customEmojiLoadAllConfirmed = false - this.scrolledGroup() + this.onScroll() this.startEmojiLoad(true) } }, @@ -142,19 +148,14 @@ const EmojiPicker = { } return 0 }, - saneAmount () { - // for UI - return LOAD_EMOJI_SANE_AMOUNT - }, filteredEmoji () { return filterByKeyword( this.$store.state.instance.customEmoji || [], this.keyword ) }, - askForSanity () { - return this.customEmojiBuffer.length >= LOAD_EMOJI_SANE_AMOUNT && - !this.customEmojiLoadAllConfirmed + customEmojiBuffer () { + return this.filteredEmoji.slice(0, this.customEmojiBufferSlice) }, emojis () { const standardEmojis = this.$store.state.instance.emoji || [] diff --git a/src/components/emoji_picker/emoji_picker.vue b/src/components/emoji_picker/emoji_picker.vue index 43da6aa23..191b9fa1b 100644 --- a/src/components/emoji_picker/emoji_picker.vue +++ b/src/components/emoji_picker/emoji_picker.vue @@ -47,7 +47,7 @@ ref="emoji-groups" class="emoji-groups" :class="groupsScrolledClass" - @scroll="scrolledGroup" + @scroll="onScroll" >
+
@@ -80,20 +81,6 @@ {{ $t('emoji.keep_open') }}
-
-
- {{ $t('emoji.load_all_hint', { saneAmount } ) }} -
- -
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue index cadef4980..01d087a27 100644 --- a/src/components/post_status_form/post_status_form.vue +++ b/src/components/post_status_form/post_status_form.vue @@ -163,7 +163,7 @@
+ >
({ + error: false + }), + mounted () { + this.redirect() + }, + methods: { + redirect () { + const acct = this.$route.params.username + '@' + this.$route.params.hostname + this.$store.state.api.backendInteractor.fetchUser({ id: acct }) + .then((externalUser) => { + if (externalUser.error) { + this.error = true + } else { + this.$store.commit('addNewUsers', [externalUser]) + const id = externalUser.id + this.$router.replace({ + name: 'external-user-profile', + params: { id } + }) + } + }) + .catch(() => { + this.error = true + }) + } + } +} + +export default RemoteUserResolver diff --git a/src/components/remote_user_resolver/remote_user_resolver.vue b/src/components/remote_user_resolver/remote_user_resolver.vue new file mode 100644 index 000000000..f8945225e --- /dev/null +++ b/src/components/remote_user_resolver/remote_user_resolver.vue @@ -0,0 +1,20 @@ + + + + + diff --git a/src/components/staff_panel/staff_panel.js b/src/components/staff_panel/staff_panel.js new file mode 100644 index 000000000..93e950adf --- /dev/null +++ b/src/components/staff_panel/staff_panel.js @@ -0,0 +1,14 @@ +import BasicUserCard from '../basic_user_card/basic_user_card.vue' + +const StaffPanel = { + components: { + BasicUserCard + }, + computed: { + staffAccounts () { + return this.$store.state.instance.staffAccounts + } + } +} + +export default StaffPanel diff --git a/src/components/staff_panel/staff_panel.vue b/src/components/staff_panel/staff_panel.vue new file mode 100644 index 000000000..1d13003df --- /dev/null +++ b/src/components/staff_panel/staff_panel.vue @@ -0,0 +1,23 @@ + + + + + diff --git a/src/components/tab_switcher/tab_switcher.js b/src/components/tab_switcher/tab_switcher.js index 3ca316b9f..008e1e954 100644 --- a/src/components/tab_switcher/tab_switcher.js +++ b/src/components/tab_switcher/tab_switcher.js @@ -12,11 +12,13 @@ export default Vue.component('tab-switcher', { }, onSwitch: { required: false, - type: Function + type: Function, + default: undefined }, activeTab: { required: false, - type: String + type: String, + default: undefined }, scrollableTabs: { required: false, diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue index 2755d89bd..6f3c958e7 100644 --- a/src/components/user_card/user_card.vue +++ b/src/components/user_card/user_card.vue @@ -276,6 +276,8 @@ mask-composite: exclude; background-size: cover; mask-size: 100% 60%; + border-top-left-radius: calc(var(--panelRadius) - 1px); + border-top-right-radius: calc(var(--panelRadius) - 1px); &.hide-bio { mask-size: 100% 40px; diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js index 32eb802e6..3fdc53403 100644 --- a/src/components/user_settings/user_settings.js +++ b/src/components/user_settings/user_settings.js @@ -35,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, @@ -56,6 +57,9 @@ const UserSettings = { backgroundPreview: null, bannerUploadError: null, backgroundUploadError: null, + changeEmailError: false, + changeEmailPassword: '', + changedEmail: false, deletingAccount: false, deleteAccountConfirmPasswordInput: '', deleteAccountError: false, @@ -305,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 423589fa8..8c18cf499 100644 --- a/src/components/user_settings/user_settings.vue +++ b/src/components/user_settings/user_settings.vue @@ -85,14 +85,14 @@ + > {{ $t('settings.hide_follows_count_description') }}

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

@@ -233,6 +233,39 @@
+
+

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

+
+

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

+ +
+
+

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

+ +
+ +

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

+ +
+

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

diff --git a/src/i18n/de.json b/src/i18n/de.json index fa9db16c7..a4b4c16f2 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 d33cdadfb..ead333c13 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -1,4 +1,23 @@ { + "about": { + "staff": "Staff", + "federation": "Federation", + "mrf_policies": "Enabled MRF Policies", + "mrf_policies_desc": "MRF policies manipulate the federation behaviour of the instance. The following policies are enabled:", + "mrf_policy_simple": "Instance-specific Policies", + "mrf_policy_simple_accept": "Accept", + "mrf_policy_simple_accept_desc": "This instance only accepts messages from the following instances:", + "mrf_policy_simple_reject": "Reject", + "mrf_policy_simple_reject_desc": "This instance will not accept messages from the following instances:", + "mrf_policy_simple_quarantine": "Quarantine", + "mrf_policy_simple_quarantine_desc": "This instance will send only public posts to the following instances:", + "mrf_policy_simple_ftl_removal": "Removal from \"The Whole Known Network\" Timeline", + "mrf_policy_simple_ftl_removal_desc": "This instance removes these instances from \"The Whole Known Network\" timeline:", + "mrf_policy_simple_media_removal": "Media Removal", + "mrf_policy_simple_media_removal_desc": "This instance removes media from posts on the following instances:", + "mrf_policy_simple_media_nsfw": "Media Force-set As Sensitive", + "mrf_policy_simple_media_nsfw_desc": "This instance forces media to be set sensitive in posts on the following instances:" + }, "chat": { "title": "Chat" }, @@ -172,6 +191,11 @@ "password_confirmation_match": "should be the same as password" } }, + "remote_user_resolver": { + "remote_user_resolver": "Remote user resolver", + "searching_for": "Searching for", + "error": "Not found." + }, "selectable_list": { "select_all": "Select all" }, @@ -219,6 +243,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 +304,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", diff --git a/src/i18n/eu.json b/src/i18n/eu.json index 90bc13a48..1c75bf754 100644 --- a/src/i18n/eu.json +++ b/src/i18n/eu.json @@ -558,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", @@ -633,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 16268425a..f8bcd9969 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/modules/config.js b/src/modules/config.js index 783141184..d4819ee8b 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -5,6 +5,7 @@ const browserLocale = (window.navigator.language || 'en').split('-')[0] export const defaultState = { colors: {}, + hideISP: false, // bad name: actually hides posts of muted USERS hideMutedPosts: undefined, // instance default collapseMessageWithSubject: undefined, // instance default diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 61cd4f16f..8f5eb4163 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' @@ -691,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() @@ -966,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 cbf48ee41..d6617276d 100644 --- a/src/services/backend_interactor_service/backend_interactor_service.js +++ b/src/services/backend_interactor_service/backend_interactor_service.js @@ -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/static/styles.json b/static/styles.json index 8996f1ee4..82fe528c3 100644 --- a/static/styles.json +++ b/static/styles.json @@ -7,11 +7,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 000000000..50b8e2f00 --- /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" + } + } +}