add option to keep some entries above the selected entry when scrolling

This commit is contained in:
Athou
2024-09-10 16:22:23 +02:00
parent ba496c1395
commit e119941762
46 changed files with 632 additions and 33 deletions

View File

@@ -166,22 +166,34 @@ export const selectEntry = createAppAsyncThunk(
})
if (arg.scrollToEntry) {
const viewMode = state.user.localSettings.viewMode
const entryIndex = state.entries.entries.indexOf(entry)
const entriesToKeepOnTopWhenScrolling =
viewMode === "expanded" ? 0 : Math.min(state.user.settings?.entriesToKeepOnTopWhenScrolling ?? 0, entryIndex)
const entryToScrollTo = state.entries.entries[entryIndex - entriesToKeepOnTopWhenScrolling]
const entryElement = document.getElementById(Constants.dom.entryId(entry))
if (entryElement) {
const entryElementToScrollTo = document.getElementById(Constants.dom.entryId(entryToScrollTo))
if (entryElement && entryElementToScrollTo) {
const scrollMode = state.user.settings?.scrollMode
const entryEntirelyVisible = Constants.layout.isTopVisible(entryElement) && Constants.layout.isBottomVisible(entryElement)
const entryEntirelyVisible =
Constants.layout.isTopVisible(entryElementToScrollTo) && Constants.layout.isBottomVisible(entryElement)
if (scrollMode === "always" || (scrollMode === "if_needed" && !entryEntirelyVisible)) {
const scrollSpeed = state.user.settings?.scrollSpeed
const margin = viewMode === "detailed" ? 8 : 3
thunkApi.dispatch(entriesSlice.actions.setScrollingToEntry(true))
scrollToEntry(entryElement, scrollSpeed, () => thunkApi.dispatch(entriesSlice.actions.setScrollingToEntry(false)))
scrollToEntry(entryElementToScrollTo, margin, scrollSpeed, () =>
thunkApi.dispatch(entriesSlice.actions.setScrollingToEntry(false))
)
}
}
}
}
)
const scrollToEntry = (entryElement: HTMLElement, scrollSpeed: number | undefined, onScrollEnded: () => void) => {
const scrollToEntry = (entryElement: HTMLElement, margin: number, scrollSpeed: number | undefined, onScrollEnded: () => void) => {
const header = document.getElementById(Constants.dom.headerId)?.getBoundingClientRect()
const offset = (header?.bottom ?? 0) + 3
const offset = (header?.bottom ?? 0) + margin
scrollToWithCallback({
options: {
top: entryElement.offsetTop - offset,

View File

@@ -3,6 +3,7 @@ import { entriesSlice } from "app/entries/slice"
import { redirectSlice } from "app/redirect/slice"
import { serverSlice } from "app/server/slice"
import { treeSlice } from "app/tree/slice"
import type { ViewMode } from "app/types"
import { userSlice } from "app/user/slice"
import { type TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"
@@ -14,7 +15,20 @@ export const reducers = {
user: userSlice.reducer,
}
export const store = configureStore({ reducer: reducers })
export const store = configureStore({
reducer: reducers,
preloadedState: {
user: {
localSettings: {
viewMode: localStorage.getItem("view-mode") as ViewMode,
},
},
},
})
store.subscribe(() => {
const state = store.getState()
localStorage.setItem("view-mode", state.user.localSettings.viewMode)
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch

View File

@@ -243,6 +243,7 @@ export interface Settings {
customJs?: string
scrollSpeed: number
scrollMode: ScrollMode
entriesToKeepOnTopWhenScrolling: number
starIconDisplayMode: IconDisplayMode
externalLinkIconDisplayMode: IconDisplayMode
markAllAsReadConfirmation: boolean

View File

@@ -1,9 +1,10 @@
import { t } from "@lingui/macro"
import { showNotification } from "@mantine/notifications"
import { createSlice, isAnyOf } from "@reduxjs/toolkit"
import type { Settings, UserModel } from "app/types"
import { type PayloadAction, createSlice, isAnyOf } from "@reduxjs/toolkit"
import type { Settings, UserModel, ViewMode } from "app/types"
import {
changeCustomContextMenu,
changeEntriesToKeepOnTopWhenScrolling,
changeExternalLinkIconDisplayMode,
changeLanguage,
changeMarkAllAsReadConfirmation,
@@ -25,16 +26,27 @@ import {
interface UserState {
settings?: Settings
localSettings: {
viewMode: ViewMode
}
profile?: UserModel
tags?: string[]
}
const initialState: UserState = {}
const initialState: UserState = {
localSettings: {
viewMode: "detailed",
},
}
export const userSlice = createSlice({
name: "user",
initialState,
reducers: {},
reducers: {
setViewMode: (state, action: PayloadAction<ViewMode>) => {
state.localSettings.viewMode = action.payload
},
},
extraReducers: builder => {
builder.addCase(reloadSettings.fulfilled, (state, action) => {
state.settings = action.payload
@@ -73,6 +85,10 @@ export const userSlice = createSlice({
if (!state.settings) return
state.settings.scrollMode = action.meta.arg
})
builder.addCase(changeEntriesToKeepOnTopWhenScrolling.pending, (state, action) => {
if (!state.settings) return
state.settings.entriesToKeepOnTopWhenScrolling = action.meta.arg
})
builder.addCase(changeStarIconDisplayMode.pending, (state, action) => {
if (!state.settings) return
state.settings.starIconDisplayMode = action.meta.arg
@@ -112,6 +128,7 @@ export const userSlice = createSlice({
changeShowRead.fulfilled,
changeScrollMarks.fulfilled,
changeScrollMode.fulfilled,
changeEntriesToKeepOnTopWhenScrolling.fulfilled,
changeStarIconDisplayMode.fulfilled,
changeExternalLinkIconDisplayMode.fulfilled,
changeMarkAllAsReadConfirmation.fulfilled,
@@ -130,3 +147,5 @@ export const userSlice = createSlice({
)
},
})
export const { setViewMode } = userSlice.actions

View File

@@ -43,6 +43,14 @@ export const changeScrollMode = createAppAsyncThunk("settings/scrollMode", (scro
if (!settings) return
client.user.saveSettings({ ...settings, scrollMode })
})
export const changeEntriesToKeepOnTopWhenScrolling = createAppAsyncThunk(
"settings/entriesToKeepOnTopWhenScrolling",
(entriesToKeepOnTopWhenScrolling: number, thunkApi) => {
const { settings } = thunkApi.getState().user
if (!settings) return
client.user.saveSettings({ ...settings, entriesToKeepOnTopWhenScrolling })
}
)
export const changeStarIconDisplayMode = createAppAsyncThunk(
"settings/starIconDisplayMode",
(starIconDisplayMode: IconDisplayMode, thunkApi) => {