Add user preference to disable sidebar swipe-to-open on mobile; cleanup migrations + README in prep for long-term fork maintenance

This commit is contained in:
2026-03-21 17:04:05 -05:00
parent eb5614a03b
commit 5cc8c736e7
12 changed files with 51 additions and 1 deletions

6
README-fork.md Normal file
View File

@@ -0,0 +1,6 @@
# `garrettmills/commafeed`
This is my personal fork of `Athou/commafeed` with some tweaks:
- "Infrequent" tab - like "All" but limits to blogs w/ an average post interval greater than a user-configurable number of days
- User preference to disable the swipe-to-open-menu gesture on mobile

View File

@@ -285,6 +285,7 @@ export interface Settings {
unreadCountTitle: boolean unreadCountTitle: boolean
unreadCountFavicon: boolean unreadCountFavicon: boolean
disablePullToRefresh: boolean disablePullToRefresh: boolean
disableMobileSwipe: boolean
infrequentThresholdDays: number infrequentThresholdDays: number
primaryColor?: string primaryColor?: string
sharingSettings: SharingSettings sharingSettings: SharingSettings

View File

@@ -4,6 +4,7 @@ import { createSlice, isAnyOf, type PayloadAction } from "@reduxjs/toolkit"
import type { LocalSettings, Settings, UserModel, ViewMode } from "@/app/types" import type { LocalSettings, Settings, UserModel, ViewMode } from "@/app/types"
import { import {
changeCustomContextMenu, changeCustomContextMenu,
changeDisableMobileSwipe,
changeDisablePullToRefresh, changeDisablePullToRefresh,
changeEntriesToKeepOnTopWhenScrolling, changeEntriesToKeepOnTopWhenScrolling,
changeExternalLinkIconDisplayMode, changeExternalLinkIconDisplayMode,
@@ -142,6 +143,10 @@ export const userSlice = createSlice({
if (!state.settings) return if (!state.settings) return
state.settings.disablePullToRefresh = action.meta.arg state.settings.disablePullToRefresh = action.meta.arg
}) })
builder.addCase(changeDisableMobileSwipe.pending, (state, action) => {
if (!state.settings) return
state.settings.disableMobileSwipe = action.meta.arg
})
builder.addCase(changeInfrequentThresholdDays.pending, (state, action) => { builder.addCase(changeInfrequentThresholdDays.pending, (state, action) => {
if (!state.settings) return if (!state.settings) return
state.settings.infrequentThresholdDays = action.meta.arg state.settings.infrequentThresholdDays = action.meta.arg
@@ -176,6 +181,7 @@ export const userSlice = createSlice({
changeUnreadCountTitle.fulfilled, changeUnreadCountTitle.fulfilled,
changeUnreadCountFavicon.fulfilled, changeUnreadCountFavicon.fulfilled,
changeDisablePullToRefresh.fulfilled, changeDisablePullToRefresh.fulfilled,
changeDisableMobileSwipe.fulfilled,
changeInfrequentThresholdDays.fulfilled, changeInfrequentThresholdDays.fulfilled,
changePrimaryColor.fulfilled, changePrimaryColor.fulfilled,
changeSharingSetting.fulfilled, changeSharingSetting.fulfilled,

View File

@@ -131,6 +131,12 @@ export const changeDisablePullToRefresh = createAppAsyncThunk(
} }
) )
export const changeDisableMobileSwipe = createAppAsyncThunk("settings/disableMobileSwipe", (disableMobileSwipe: boolean, thunkApi) => {
const { settings } = thunkApi.getState().user
if (!settings) return
client.user.saveSettings({ ...settings, disableMobileSwipe })
})
export const changePrimaryColor = createAppAsyncThunk("settings/primaryColor", (primaryColor: string, thunkApi) => { export const changePrimaryColor = createAppAsyncThunk("settings/primaryColor", (primaryColor: string, thunkApi) => {
const { settings } = thunkApi.getState().user const { settings } = thunkApi.getState().user
if (!settings) return if (!settings) return

View File

@@ -9,6 +9,7 @@ import { useAppDispatch, useAppSelector } from "@/app/store"
import type { IconDisplayMode, ScrollMode, SharingSettings } from "@/app/types" import type { IconDisplayMode, ScrollMode, SharingSettings } from "@/app/types"
import { import {
changeCustomContextMenu, changeCustomContextMenu,
changeDisableMobileSwipe,
changeDisablePullToRefresh, changeDisablePullToRefresh,
changeEntriesToKeepOnTopWhenScrolling, changeEntriesToKeepOnTopWhenScrolling,
changeExternalLinkIconDisplayMode, changeExternalLinkIconDisplayMode,
@@ -45,6 +46,7 @@ export function DisplaySettings() {
const unreadCountTitle = useAppSelector(state => state.user.settings?.unreadCountTitle) const unreadCountTitle = useAppSelector(state => state.user.settings?.unreadCountTitle)
const unreadCountFavicon = useAppSelector(state => state.user.settings?.unreadCountFavicon) const unreadCountFavicon = useAppSelector(state => state.user.settings?.unreadCountFavicon)
const disablePullToRefresh = useAppSelector(state => state.user.settings?.disablePullToRefresh) const disablePullToRefresh = useAppSelector(state => state.user.settings?.disablePullToRefresh)
const disableMobileSwipe = useAppSelector(state => state.user.settings?.disableMobileSwipe)
const infrequentThresholdDays = useAppSelector(state => state.user.settings?.infrequentThresholdDays) const infrequentThresholdDays = useAppSelector(state => state.user.settings?.infrequentThresholdDays)
const sharingSettings = useAppSelector(state => state.user.settings?.sharingSettings) const sharingSettings = useAppSelector(state => state.user.settings?.sharingSettings)
const primaryColor = useAppSelector(state => state.user.settings?.primaryColor) || Constants.theme.defaultPrimaryColor const primaryColor = useAppSelector(state => state.user.settings?.primaryColor) || Constants.theme.defaultPrimaryColor
@@ -145,6 +147,12 @@ export function DisplaySettings() {
onChange={async e => await dispatch(changeMobileFooter(e.currentTarget.checked))} onChange={async e => await dispatch(changeMobileFooter(e.currentTarget.checked))}
/> />
<Switch
label={<Trans>On mobile, disable swipe gesture to open the menu</Trans>}
checked={disableMobileSwipe}
onChange={async e => await dispatch(changeDisableMobileSwipe(e.currentTarget.checked))}
/>
<NumberInput <NumberInput
label={<Trans>Infrequent posts threshold (days)</Trans>} label={<Trans>Infrequent posts threshold (days)</Trans>}
description={<Trans>Feeds posting less often than this (on average) will appear in the Infrequent view</Trans>} description={<Trans>Feeds posting less often than this (on average) will appear in the Infrequent view</Trans>}

View File

@@ -715,6 +715,10 @@ msgstr "On desktop"
msgid "On mobile" msgid "On mobile"
msgstr "On mobile" msgstr "On mobile"
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile, disable swipe gesture to open the menu"
msgstr "On mobile, disable swipe gesture to open the menu"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen" msgid "On mobile, show action buttons at the bottom of the screen"
msgstr "On mobile, show action buttons at the bottom of the screen" msgstr "On mobile, show action buttons at the bottom of the screen"

View File

@@ -79,6 +79,7 @@ export default function Layout(props: Readonly<LayoutProps>) {
const webSocketConnected = useAppSelector(state => state.server.webSocketConnected) const webSocketConnected = useAppSelector(state => state.server.webSocketConnected)
const treeReloadInterval = useAppSelector(state => state.server.serverInfos?.treeReloadInterval) const treeReloadInterval = useAppSelector(state => state.server.serverInfos?.treeReloadInterval)
const mobileFooter = useAppSelector(state => state.user.settings?.mobileFooter) const mobileFooter = useAppSelector(state => state.user.settings?.mobileFooter)
const disableMobileSwipe = useAppSelector(state => state.user.settings?.disableMobileSwipe)
const sidebarWidth = useAppSelector(state => state.user.localSettings.sidebarWidth) const sidebarWidth = useAppSelector(state => state.user.localSettings.sidebarWidth)
const headerInFooter = mobile && !isBrowserExtensionPopup && mobileFooter const headerInFooter = mobile && !isBrowserExtensionPopup && mobileFooter
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
@@ -164,6 +165,9 @@ export default function Layout(props: Readonly<LayoutProps>) {
const swipeHandlers = useSwipeable({ const swipeHandlers = useSwipeable({
onSwiping: e => { onSwiping: e => {
if (disableMobileSwipe) {
return
}
const threshold = document.documentElement.clientWidth / 6 const threshold = document.documentElement.clientWidth / 6
if (e.absX > threshold) { if (e.absX > threshold) {
dispatch(setMobileMenuOpen(e.dir === "Right")) dispatch(setMobileMenuOpen(e.dir === "Right"))

View File

@@ -145,6 +145,7 @@ public class UserSettings extends AbstractModel {
private boolean unreadCountTitle; private boolean unreadCountTitle;
private boolean unreadCountFavicon; private boolean unreadCountFavicon;
private boolean disablePullToRefresh; private boolean disablePullToRefresh;
private boolean disableMobileSwipe;
private int infrequentThresholdDays; private int infrequentThresholdDays;

View File

@@ -76,6 +76,9 @@ public class Settings implements Serializable {
@Schema(description = "disable pull to refresh", required = true) @Schema(description = "disable pull to refresh", required = true)
private boolean disablePullToRefresh; private boolean disablePullToRefresh;
@Schema(description = "disable swipe gesture to open mobile menu", required = true)
private boolean disableMobileSwipe;
@Schema(description = "threshold in days for the infrequent view", required = true) @Schema(description = "threshold in days for the infrequent view", required = true)
private int infrequentThresholdDays; private int infrequentThresholdDays;

View File

@@ -132,6 +132,7 @@ public class UserREST {
s.setUnreadCountTitle(settings.isUnreadCountTitle()); s.setUnreadCountTitle(settings.isUnreadCountTitle());
s.setUnreadCountFavicon(settings.isUnreadCountFavicon()); s.setUnreadCountFavicon(settings.isUnreadCountFavicon());
s.setDisablePullToRefresh(settings.isDisablePullToRefresh()); s.setDisablePullToRefresh(settings.isDisablePullToRefresh());
s.setDisableMobileSwipe(settings.isDisableMobileSwipe());
s.setPrimaryColor(settings.getPrimaryColor()); s.setPrimaryColor(settings.getPrimaryColor());
s.setInfrequentThresholdDays(settings.getInfrequentThresholdDays()); s.setInfrequentThresholdDays(settings.getInfrequentThresholdDays());
@@ -169,6 +170,7 @@ public class UserREST {
s.setUnreadCountTitle(false); s.setUnreadCountTitle(false);
s.setUnreadCountFavicon(true); s.setUnreadCountFavicon(true);
s.setDisablePullToRefresh(false); s.setDisablePullToRefresh(false);
s.setDisableMobileSwipe(false);
s.setInfrequentThresholdDays(7); s.setInfrequentThresholdDays(7);
} }
return s; return s;
@@ -206,6 +208,7 @@ public class UserREST {
s.setUnreadCountTitle(settings.isUnreadCountTitle()); s.setUnreadCountTitle(settings.isUnreadCountTitle());
s.setUnreadCountFavicon(settings.isUnreadCountFavicon()); s.setUnreadCountFavicon(settings.isUnreadCountFavicon());
s.setDisablePullToRefresh(settings.isDisablePullToRefresh()); s.setDisablePullToRefresh(settings.isDisablePullToRefresh());
s.setDisableMobileSwipe(settings.isDisableMobileSwipe());
s.setPrimaryColor(settings.getPrimaryColor()); s.setPrimaryColor(settings.getPrimaryColor());
s.setInfrequentThresholdDays(settings.getInfrequentThresholdDays()); s.setInfrequentThresholdDays(settings.getInfrequentThresholdDays());

View File

@@ -11,4 +11,12 @@
</addColumn> </addColumn>
</changeSet> </changeSet>
<changeSet id="add-disable-mobile-swipe" author="athou">
<addColumn tableName="USERSETTINGS">
<column name="disableMobileSwipe" type="BOOLEAN" valueBoolean="false">
<constraints nullable="false" />
</column>
</addColumn>
</changeSet>
</databaseChangeLog> </databaseChangeLog>

View File

@@ -38,6 +38,6 @@
<include file="changelogs/db.changelog-5.11.xml" /> <include file="changelogs/db.changelog-5.11.xml" />
<include file="changelogs/db.changelog-5.12.xml" /> <include file="changelogs/db.changelog-5.12.xml" />
<include file="changelogs/db.changelog-7.0.xml" /> <include file="changelogs/db.changelog-7.0.xml" />
<include file="changelogs/db.changelog-7.1.xml" /> <include file="changelogs/db.changelog-gmfork.xml" />
</databaseChangeLog> </databaseChangeLog>