From 3dd2570c2d3d1aa8b1eef991fd93259992018f32 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 4 Aug 2026 17:17:05 +0300 Subject: [PATCH 01/90] cherry-pick this into 2.11.3 fixes --- src/components/moderation_tools/moderation_tools.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/moderation_tools/moderation_tools.js b/src/components/moderation_tools/moderation_tools.js index ca4852ab3..ba13afc8b 100644 --- a/src/components/moderation_tools/moderation_tools.js +++ b/src/components/moderation_tools/moderation_tools.js @@ -405,7 +405,7 @@ const ModerationTools = { ) }, isAdmin() { - this.$store.state.users.currentUser.role === 'admin' + return this.$store.state.users.currentUser.role === 'admin' }, }, methods: { From ab2a011183f6c4808acad62f7ed65ba37ebd076c Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 4 Aug 2026 17:22:33 +0300 Subject: [PATCH 02/90] also cherry-pick? --- .../notifications_fetcher/notifications_fetcher.service.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js index 8530c468c..0e0bc0277 100644 --- a/src/services/notifications_fetcher/notifications_fetcher.service.js +++ b/src/services/notifications_fetcher/notifications_fetcher.service.js @@ -66,7 +66,8 @@ const fetchAndUpdate = ({ store, credentials, older = false, sinceId }) => { const unreadNotifsIds = notifications .filter((n) => !n.seen) .map((n) => n.id) - if (readNotifsIds.length > 0 && readNotifsIds.length > 0) { + + if (readNotifsIds.length > 0 && unreadNotifsIds.length > 0) { const minId = Math.min(...unreadNotifsIds) // Oldest known unread notification if (minId !== Infinity) { args.sinceId = null // Don't use since_id since it sorta conflicts with min_id From 4c8b5fe68b911621b4dde49de2041147c8a9da33 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 4 Aug 2026 17:32:02 +0300 Subject: [PATCH 03/90] fix domain unmuting --- changelog.d/fix-domain-mute.fix | 1 + changelog.d/moderation-tools.fix | 1 + src/modules/users.js | 6 ++++-- 3 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 changelog.d/fix-domain-mute.fix create mode 100644 changelog.d/moderation-tools.fix diff --git a/changelog.d/fix-domain-mute.fix b/changelog.d/fix-domain-mute.fix new file mode 100644 index 000000000..1cb910d22 --- /dev/null +++ b/changelog.d/fix-domain-mute.fix @@ -0,0 +1 @@ +domain mute fixedw diff --git a/changelog.d/moderation-tools.fix b/changelog.d/moderation-tools.fix new file mode 100644 index 000000000..1417048a5 --- /dev/null +++ b/changelog.d/moderation-tools.fix @@ -0,0 +1 @@ +fix moderation tools button possibly not appearing for admins diff --git a/src/modules/users.js b/src/modules/users.js index b58566859..0ecee269f 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -40,6 +40,8 @@ import { muteUser as apiMuteUser, unblockUser as apiUnblockUser, unmuteUser as apiUnmuteUser, + muteDomain as apiMuteDomain, + unmuteDomain as apiUnmuteDomain, fetchBlocks, fetchDomainMutes, fetchMutes, @@ -170,14 +172,14 @@ const showReblogs = (store, userId) => { } const muteDomain = (store, domain) => { - return muteDomain({ + return apiMuteDomain({ domain, credentials: useOAuthStore().token, }).then(() => store.commit('addDomainMute', domain)) } const unmuteDomain = (store, domain) => { - return unmuteDomain({ + return apiUnmuteDomain({ domain, credentials: useOAuthStore().token, }).then(() => store.commit('removeDomainMute', domain)) From 69da111951a1df3191077f48c89e96d74bf20947 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 5 Aug 2026 18:39:49 +0300 Subject: [PATCH 04/90] fix #3525 --- src/components/image_cropper/image_cropper.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/image_cropper/image_cropper.js b/src/components/image_cropper/image_cropper.js index c8529c8e6..c7a213a4f 100644 --- a/src/components/image_cropper/image_cropper.js +++ b/src/components/image_cropper/image_cropper.js @@ -70,6 +70,9 @@ const ImageCropper = { ) }, onCropperSelectionChange(event) { + if (!this.$refs.cropperCanvas) { + return // Cropper sends even before component is fully initialized + } const cropperCanvas = this.$refs.cropperCanvas const cropperCanvasRect = cropperCanvas.getBoundingClientRect() const selection = event.detail From 15a59100656e79ae3acc60cabca431a030d76ba4 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 5 Aug 2026 18:40:53 +0300 Subject: [PATCH 05/90] changelog --- changelog.d/avatar_upload.fix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/avatar_upload.fix diff --git a/changelog.d/avatar_upload.fix b/changelog.d/avatar_upload.fix new file mode 100644 index 000000000..b5dd9ee56 --- /dev/null +++ b/changelog.d/avatar_upload.fix @@ -0,0 +1 @@ +fix image cropper error preventing avatar upload From 05d3fdd62ca5f90b15ec6bfb86c9c87f15b1ff84 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 5 Aug 2026 19:13:07 +0300 Subject: [PATCH 06/90] fix third column setting change not affecting UI immideately --- src/App.js | 24 ++++++++++++------- src/components/notifications/notifications.js | 6 +++-- .../settings_modal/helpers/setting.js | 5 +++- .../settings_modal/tabs/layout_tab.js | 6 +++++ .../settings_modal/tabs/layout_tab.vue | 1 + 5 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/App.js b/src/App.js index 4ce6f1abc..c5b38d958 100644 --- a/src/App.js +++ b/src/App.js @@ -74,6 +74,8 @@ export default { }, data: () => ({ mobileActivePanel: 'timeline', + updateMobileState: null, + updateScrollState: null, }), provide() { return { @@ -211,22 +213,26 @@ export default { hideShoutbox() { return this.isChats || useMergedConfigStore().mergedConfig.hideShoutbox }, + thirdColumnMode() { + return this.mergedConfig.thirdColumnMode + }, + reverseSetting() { + return this.mergedConfig.sidebarRight + }, reverseLayout() { - const { thirdColumnMode, sidebarRight: reverseSetting } = - useMergedConfigStore().mergedConfig if (this.layoutType !== 'wide') { - return reverseSetting + return this.reverseSetting } else { - return thirdColumnMode === 'notifications' - ? reverseSetting - : !reverseSetting + return this.thirdColumnMode === 'notifications' + ? this.reverseSetting + : !this.reverseSetting } }, noSticky() { - return useMergedConfigStore().mergedConfig.disableStickyHeaders + return this.mergedConfig.disableStickyHeaders }, showScrollbars() { - return useMergedConfigStore().mergedConfig.showScrollbars + return this.mergedConfig.showScrollbars }, scrollParent() { return window /* this.$refs.appContentRef */ @@ -234,7 +240,7 @@ export default { showInstanceSpecificPanel() { return ( this.instanceSpecificPanelPresent && - !useMergedConfigStore().mergedConfig.hideISP + !this.mergedConfig.hideISP ) }, ...mapState(useMergedConfigStore, ['mergedConfig']), diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js index 393aef639..5ae6014bc 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -134,12 +134,11 @@ const Notifications = { return this.minimalMode || layoutType === 'mobile' }, teleportTarget() { - const { layoutType } = useInterfaceStore() const map = { wide: '#notifs-column', mobile: '#mobile-notifications', } - return map[layoutType] || '#notifs-sidebar' + return map[this.layoutType] || '#notifs-sidebar' }, popoversZLayer() { const { layoutType } = useInterfaceStore() @@ -162,6 +161,9 @@ const Notifications = { }, ...mapState(useAnnouncementsStore, ['unreadAnnouncementCount']), ...mapState(useChatsStore, ['unreadChatsCount']), + ...mapState(useInterfaceStore, [ + 'layoutType', + ]), }, mounted() { this.scrollerRef = this.$refs.root.closest('.column.-scrollable') diff --git a/src/components/settings_modal/helpers/setting.js b/src/components/settings_modal/helpers/setting.js index c2f5279f6..6b7f33925 100644 --- a/src/components/settings_modal/helpers/setting.js +++ b/src/components/settings_modal/helpers/setting.js @@ -113,6 +113,7 @@ export default { localDraft: null, } }, + emits: ['update:modelValue'], created() { if ( this.realDraftMode && @@ -244,7 +245,7 @@ export default { }, configSink() { if (this.path == null) { - return (k, v) => this.$emit('update:modelValue', v) + return () => {} } switch (this.realSource) { @@ -385,11 +386,13 @@ export default { if (this.realDraftMode) { this.draft = this.getValue(e) } else { + this.$emit('update:modelValue', this.getValue(e)) this.configSink(this.path, this.getValue(e)) } }, commitDraft() { if (this.realDraftMode) { + this.$emit('update:modelValue', v) this.configSink(this.path, this.draft) } }, diff --git a/src/components/settings_modal/tabs/layout_tab.js b/src/components/settings_modal/tabs/layout_tab.js index fc56e1392..c1a3cd3b4 100644 --- a/src/components/settings_modal/tabs/layout_tab.js +++ b/src/components/settings_modal/tabs/layout_tab.js @@ -7,6 +7,7 @@ import UnitSetting from '../helpers/unit_setting.vue' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' +import { useInterfaceStore } from 'src/stores/interface.js' const GeneralTab = { data() { @@ -46,6 +47,11 @@ const GeneralTab = { }, ...SharedComputedObject(), }, + methods: { + updateLayout() { + useInterfaceStore().setLayoutWidth() + }, + }, } export default GeneralTab diff --git a/src/components/settings_modal/tabs/layout_tab.vue b/src/components/settings_modal/tabs/layout_tab.vue index 0035f3a11..b649572fb 100644 --- a/src/components/settings_modal/tabs/layout_tab.vue +++ b/src/components/settings_modal/tabs/layout_tab.vue @@ -105,6 +105,7 @@ id="thirdColumnMode" path="thirdColumnMode" :options="thirdColumnModeOptions" + @change="updateLayout" > {{ $t('settings.third_column_mode') }} From 5b6f55e15ed578d643f63eb370500993a00b597b Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 5 Aug 2026 19:13:58 +0300 Subject: [PATCH 07/90] changelog --- changelog.d/third_column.fix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/third_column.fix diff --git a/changelog.d/third_column.fix b/changelog.d/third_column.fix new file mode 100644 index 000000000..191e914d5 --- /dev/null +++ b/changelog.d/third_column.fix @@ -0,0 +1 @@ +Fixed layout selector (third column/reverse) not working immideately upon change From b4c00f4263d56f4dfe035b6e912fe486020968cf Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 5 Aug 2026 19:15:37 +0300 Subject: [PATCH 08/90] lint --- src/App.js | 5 +---- src/components/notifications/notifications.js | 4 +--- src/components/settings_modal/helpers/setting.js | 4 ++-- src/components/settings_modal/tabs/layout_tab.js | 2 +- 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/App.js b/src/App.js index c5b38d958..d7a63bd8a 100644 --- a/src/App.js +++ b/src/App.js @@ -238,10 +238,7 @@ export default { return window /* this.$refs.appContentRef */ }, showInstanceSpecificPanel() { - return ( - this.instanceSpecificPanelPresent && - !this.mergedConfig.hideISP - ) + return this.instanceSpecificPanelPresent && !this.mergedConfig.hideISP }, ...mapState(useMergedConfigStore, ['mergedConfig']), ...mapState(useInterfaceStore, [ diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js index 5ae6014bc..feb59857e 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -161,9 +161,7 @@ const Notifications = { }, ...mapState(useAnnouncementsStore, ['unreadAnnouncementCount']), ...mapState(useChatsStore, ['unreadChatsCount']), - ...mapState(useInterfaceStore, [ - 'layoutType', - ]), + ...mapState(useInterfaceStore, ['layoutType']), }, mounted() { this.scrollerRef = this.$refs.root.closest('.column.-scrollable') diff --git a/src/components/settings_modal/helpers/setting.js b/src/components/settings_modal/helpers/setting.js index 6b7f33925..92d36b207 100644 --- a/src/components/settings_modal/helpers/setting.js +++ b/src/components/settings_modal/helpers/setting.js @@ -245,7 +245,7 @@ export default { }, configSink() { if (this.path == null) { - return () => {} + return () => {/* no-op */} } switch (this.realSource) { @@ -392,7 +392,7 @@ export default { }, commitDraft() { if (this.realDraftMode) { - this.$emit('update:modelValue', v) + this.$emit('update:modelValue', this.draft) this.configSink(this.path, this.draft) } }, diff --git a/src/components/settings_modal/tabs/layout_tab.js b/src/components/settings_modal/tabs/layout_tab.js index c1a3cd3b4..50761fa43 100644 --- a/src/components/settings_modal/tabs/layout_tab.js +++ b/src/components/settings_modal/tabs/layout_tab.js @@ -6,8 +6,8 @@ import SharedComputedObject from '../helpers/shared_computed_object.js' import UnitSetting from '../helpers/unit_setting.vue' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' -import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useInterfaceStore } from 'src/stores/interface.js' +import { useMergedConfigStore } from 'src/stores/merged_config.js' const GeneralTab = { data() { From fbcfbb757c35ec17c954e5c2c9205976541d14e1 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 5 Aug 2026 19:18:33 +0300 Subject: [PATCH 09/90] lint --- src/components/settings_modal/helpers/setting.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/settings_modal/helpers/setting.js b/src/components/settings_modal/helpers/setting.js index 92d36b207..f7cd1ca91 100644 --- a/src/components/settings_modal/helpers/setting.js +++ b/src/components/settings_modal/helpers/setting.js @@ -245,7 +245,9 @@ export default { }, configSink() { if (this.path == null) { - return () => {/* no-op */} + return () => { + /* no-op */ + } } switch (this.realSource) { From 0fbf6704f27b62ce9210a8f03cdfe5d66ee13571 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 5 Aug 2026 19:56:57 +0300 Subject: [PATCH 10/90] fix cjk pinned display --- src/components/status/status.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/status/status.scss b/src/components/status/status.scss index 1214d8b12..1b0e3cf3e 100644 --- a/src/components/status/status.scss +++ b/src/components/status/status.scss @@ -119,6 +119,7 @@ display: flex; flex-shrink: 0; align-self: baseline; + word-break: keep-all; .button-unstyled { padding: 0.2em; From d64daed77de1fad69ec4cdae32a85fd6bbae912e Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 5 Aug 2026 19:57:34 +0300 Subject: [PATCH 11/90] changelog --- changelog.d/cjk.fix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/cjk.fix diff --git a/changelog.d/cjk.fix b/changelog.d/cjk.fix new file mode 100644 index 000000000..861c6cb91 --- /dev/null +++ b/changelog.d/cjk.fix @@ -0,0 +1 @@ +fix pinned indicator looking wrong on CJK locales From 32b9c4a14d0e94907d1ba1e0ac588f3714eded4e Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 10 Aug 2026 13:52:45 +0300 Subject: [PATCH 12/90] initial pinia implementation --- src/components/registration/registration.js | 65 +- src/components/registration/registration.vue | 22 +- src/stores/users.js | 671 +++++++++++++++++++ 3 files changed, 735 insertions(+), 23 deletions(-) create mode 100644 src/stores/users.js diff --git a/src/components/registration/registration.js b/src/components/registration/registration.js index ea07c662b..1dbc99859 100644 --- a/src/components/registration/registration.js +++ b/src/components/registration/registration.js @@ -1,14 +1,16 @@ import useVuelidate from '@vuelidate/core' import { required, requiredIf, sameAs } from '@vuelidate/validators' -import { mapState as mapPiniaState } from 'pinia' -import { mapActions, mapState } from 'vuex' +import { mapActions, mapState } from 'pinia' import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue' import TermsOfServicePanel from 'src/components/terms_of_service_panel/terms_of_service_panel.vue' import localeService from '../../services/locale/locale.service.js' import { useInstanceStore } from 'src/stores/instance.js' +import { useOAuthStore } from 'src/stores/oauth.js' +import { useUsersStore } from 'src/stores/users.js' +import { getCaptcha, register } from 'src/api/public.js' import { DAY } from 'src/services/date_utils/date_utils.js' const registration = { @@ -26,6 +28,9 @@ const registration = { reason: '', language: [''], }, + signUpPending: false, + signUpErrors: [], + signUpNotice: {}, captcha: {}, }), components: { @@ -58,7 +63,7 @@ const registration = { } }, created() { - if ((!this.registrationOpen && !this.token) || this.signedIn) { + if ((!this.registrationOpen && !this.token) || this.loggedIn) { this.$router.push({ name: 'root' }) } @@ -100,7 +105,10 @@ const registration = { ) ) }, - ...mapPiniaState(useInstanceStore, { + hasSignUpNotice(state) { + return this.signUpNotice.message + }, + ...mapState(useInstanceStore, { registrationOpen: (store) => store.registrationOpen, embeddedToS: (store) => store.embeddedToS, termsOfService: (store) => store.tos, @@ -109,16 +117,49 @@ const registration = { birthdayRequired: (store) => store.birthdayRequired, birthdayMinAge: (store) => store.birthdayMinAge, }), - ...mapState({ - signedIn: (state) => !!state.users.currentUser, - isPending: (state) => state.users.signUpPending, - serverValidationErrors: (state) => state.users.signUpErrors, - signUpNotice: (state) => state.users.signUpNotice, - hasSignUpNotice: (state) => !!state.users.signUpNotice.message, - }), + ...mapState(useUsersStore, ['loggedIn']), }, methods: { - ...mapActions(['signUp', 'getCaptcha']), + ...mapActions(useUsersStore, ['loginUser']), + getCaptcha(store) { + return getCaptcha({ + credentials: useOAuthStore().token, + }).then(({ data }) => data) + }, + async signUp(userInfo) { + const oauthStore = useOAuthStore() + + this.signUpPending = true + this.signUpErrors = [] + this.signUpNotice = {} + + try { + const token = await oauthStore.ensureAppToken() + const { data } = await register({ + credentials: token, + params: { ...userInfo }, + }) + + if (data.access_token) { + this.signUpPending = false + oauthStore.setToken(data.access_token) + await this.loginUser(data.access_token) + return 'ok' + } else { + // Request succeeded, but user cannot login yet. + this.signUpErrors = [] + this.signUpNotice = data + return 'request_sent' + } + } catch (e) { + const errors = e.message + this.signUpErrors = errors + this.signUpNotice = {} + throw e + } finally { + this.signUpPending = false + } + }, async submit() { this.user.nickname = this.user.username this.user.token = this.token diff --git a/src/components/registration/registration.vue b/src/components/registration/registration.vue index b969a416e..0abcfe656 100644 --- a/src/components/registration/registration.vue +++ b/src/components/registration/registration.vue @@ -28,7 +28,7 @@ @@ -126,7 +126,7 @@ @@ -259,7 +259,7 @@