mirror of
https://github.com/Athou/commafeed.git
synced 2026-03-21 21:37:29 +00:00
select and mark entry as read when scrolling in expanded view
This commit is contained in:
@@ -16,7 +16,8 @@ import { useAppDispatch, useAppSelector } from "app/store"
|
||||
import { KeyboardShortcutsHelp } from "components/KeyboardShortcutsHelp"
|
||||
import { Loader } from "components/Loader"
|
||||
import { useMousetrap } from "hooks/useMousetrap"
|
||||
import { useEffect, useRef } from "react"
|
||||
import throttle from "lodash/throttle"
|
||||
import { useEffect } from "react"
|
||||
import InfiniteScroll from "react-infinite-scroller"
|
||||
import { FeedEntry } from "./FeedEntry"
|
||||
|
||||
@@ -27,7 +28,8 @@ export function FeedEntries() {
|
||||
const selectedEntryId = useAppSelector(state => state.entries.selectedEntryId)
|
||||
const hasMore = useAppSelector(state => state.entries.hasMore)
|
||||
const viewMode = useAppSelector(state => state.user.settings?.viewMode)
|
||||
const scrollSpeed = useAppSelector(state => state.user.settings?.scrollSpeed)
|
||||
const scrollMarks = useAppSelector(state => state.user.settings?.scrollMarks)
|
||||
const scrollingToEntry = useAppSelector(state => state.entries.scrollingToEntry)
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const selectedEntry = entries.find(e => e.id === selectedEntryId)
|
||||
@@ -46,80 +48,90 @@ export function FeedEntries() {
|
||||
entry,
|
||||
expand: !entry.expanded,
|
||||
markAsRead: !entry.expanded,
|
||||
scrollToEntry: true,
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// references to entries html elements
|
||||
const refs = useRef<{ [id: string]: HTMLDivElement }>({})
|
||||
useEffect(() => {
|
||||
// remove refs that are not in entries anymore
|
||||
Object.keys(refs.current).forEach(k => {
|
||||
const found = entries.some(e => e.id === k)
|
||||
if (!found) delete refs.current[k]
|
||||
})
|
||||
}, [entries])
|
||||
const scrollArea = document.getElementById(Constants.dom.mainScrollAreaId)
|
||||
|
||||
// scroll to entry when selected entry changes
|
||||
useEffect(() => {
|
||||
if (!selectedEntryId) return
|
||||
if (!selectedEntry?.expanded) return
|
||||
const listener = () => {
|
||||
if (viewMode !== "expanded") return
|
||||
if (scrollingToEntry) return
|
||||
|
||||
const selectedEntryElement = refs.current[selectedEntryId]
|
||||
if (Constants.layout.isTopVisible(selectedEntryElement) && Constants.layout.isBottomVisible(selectedEntryElement)) return
|
||||
const currentEntry = entries
|
||||
// use slice to get a copy of the array because reverse mutates the array in-place
|
||||
.slice()
|
||||
.reverse()
|
||||
.find(e => {
|
||||
const el = document.getElementById(Constants.dom.entryId(e))
|
||||
return el && !Constants.layout.isTopVisible(el)
|
||||
})
|
||||
if (currentEntry) {
|
||||
dispatch(
|
||||
selectEntry({
|
||||
entry: currentEntry,
|
||||
expand: false,
|
||||
markAsRead: !!scrollMarks,
|
||||
scrollToEntry: false,
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
const throttledListener = throttle(listener, 100)
|
||||
scrollArea?.addEventListener("scroll", throttledListener)
|
||||
return () => scrollArea?.removeEventListener("scroll", throttledListener)
|
||||
}, [dispatch, entries, viewMode, scrollMarks, scrollingToEntry])
|
||||
|
||||
document.getElementById(Constants.dom.mainScrollAreaId)?.scrollTo({
|
||||
// having a small gap between the top of the content and the top of the page is sexier
|
||||
top: selectedEntryElement.offsetTop - 3,
|
||||
behavior: scrollSpeed && scrollSpeed > 0 ? "smooth" : "auto",
|
||||
})
|
||||
}, [selectedEntryId, selectedEntry?.expanded, scrollSpeed])
|
||||
|
||||
useMousetrap("r", () => {
|
||||
dispatch(reloadEntries())
|
||||
})
|
||||
useMousetrap("j", () => {
|
||||
useMousetrap("r", () => dispatch(reloadEntries()))
|
||||
useMousetrap("j", () =>
|
||||
dispatch(
|
||||
selectNextEntry({
|
||||
expand: true,
|
||||
markAsRead: true,
|
||||
scrollToEntry: true,
|
||||
})
|
||||
)
|
||||
})
|
||||
useMousetrap("n", () => {
|
||||
)
|
||||
useMousetrap("n", () =>
|
||||
dispatch(
|
||||
selectNextEntry({
|
||||
expand: false,
|
||||
markAsRead: false,
|
||||
scrollToEntry: true,
|
||||
})
|
||||
)
|
||||
})
|
||||
useMousetrap("k", () => {
|
||||
)
|
||||
useMousetrap("k", () =>
|
||||
dispatch(
|
||||
selectPreviousEntry({
|
||||
expand: true,
|
||||
markAsRead: true,
|
||||
scrollToEntry: true,
|
||||
})
|
||||
)
|
||||
})
|
||||
useMousetrap("p", () => {
|
||||
)
|
||||
useMousetrap("p", () =>
|
||||
dispatch(
|
||||
selectPreviousEntry({
|
||||
expand: false,
|
||||
markAsRead: false,
|
||||
scrollToEntry: true,
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
useMousetrap("space", () => {
|
||||
if (selectedEntry) {
|
||||
if (selectedEntry.expanded) {
|
||||
const ref = refs.current[selectedEntry.id]
|
||||
if (Constants.layout.isBottomVisible(ref)) {
|
||||
const entryElement = document.getElementById(Constants.dom.entryId(selectedEntry))
|
||||
if (entryElement && Constants.layout.isBottomVisible(entryElement)) {
|
||||
dispatch(
|
||||
selectNextEntry({
|
||||
expand: true,
|
||||
markAsRead: true,
|
||||
scrollToEntry: true,
|
||||
})
|
||||
)
|
||||
} else {
|
||||
@@ -135,6 +147,7 @@ export function FeedEntries() {
|
||||
entry: selectedEntry,
|
||||
expand: true,
|
||||
markAsRead: true,
|
||||
scrollToEntry: true,
|
||||
})
|
||||
)
|
||||
}
|
||||
@@ -143,6 +156,7 @@ export function FeedEntries() {
|
||||
selectNextEntry({
|
||||
expand: true,
|
||||
markAsRead: true,
|
||||
scrollToEntry: true,
|
||||
})
|
||||
)
|
||||
}
|
||||
@@ -150,12 +164,13 @@ export function FeedEntries() {
|
||||
useMousetrap("shift+space", () => {
|
||||
if (selectedEntry) {
|
||||
if (selectedEntry.expanded) {
|
||||
const ref = refs.current[selectedEntry.id]
|
||||
if (Constants.layout.isTopVisible(ref)) {
|
||||
const entryElement = document.getElementById(Constants.dom.entryId(selectedEntry))
|
||||
if (entryElement && Constants.layout.isTopVisible(entryElement)) {
|
||||
dispatch(
|
||||
selectPreviousEntry({
|
||||
expand: true,
|
||||
markAsRead: true,
|
||||
scrollToEntry: true,
|
||||
})
|
||||
)
|
||||
} else {
|
||||
@@ -170,6 +185,7 @@ export function FeedEntries() {
|
||||
selectPreviousEntry({
|
||||
expand: true,
|
||||
markAsRead: true,
|
||||
scrollToEntry: true,
|
||||
})
|
||||
)
|
||||
}
|
||||
@@ -183,6 +199,7 @@ export function FeedEntries() {
|
||||
entry: selectedEntry,
|
||||
expand: !selectedEntry.expanded,
|
||||
markAsRead: !selectedEntry.expanded,
|
||||
scrollToEntry: true,
|
||||
})
|
||||
)
|
||||
})
|
||||
@@ -222,12 +239,8 @@ export function FeedEntries() {
|
||||
})
|
||||
)
|
||||
})
|
||||
useMousetrap("g a", () => {
|
||||
dispatch(redirectToRootCategory())
|
||||
})
|
||||
useMousetrap("?", () => {
|
||||
openModal({ title: t`Keyboard shortcuts`, size: "xl", children: <KeyboardShortcutsHelp /> })
|
||||
})
|
||||
useMousetrap("g a", () => dispatch(redirectToRootCategory()))
|
||||
useMousetrap("?", () => openModal({ title: t`Keyboard shortcuts`, size: "xl", children: <KeyboardShortcutsHelp /> }))
|
||||
|
||||
if (!entries) return <Loader />
|
||||
return (
|
||||
@@ -243,7 +256,7 @@ export function FeedEntries() {
|
||||
<div
|
||||
key={entry.id}
|
||||
ref={el => {
|
||||
if (el) refs.current[entry.id] = el
|
||||
if (el) el.id = Constants.dom.entryId(entry)
|
||||
}}
|
||||
>
|
||||
<FeedEntry
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { t } from "@lingui/macro"
|
||||
import { Divider, Select, SimpleGrid, Stack, Switch } from "@mantine/core"
|
||||
import { Constants } from "app/constants"
|
||||
import { changeLanguage, changeScrollSpeed, changeSharingSetting, changeShowRead } from "app/slices/user"
|
||||
import { changeLanguage, changeScrollMarks, changeScrollSpeed, changeSharingSetting, changeShowRead } from "app/slices/user"
|
||||
import { useAppDispatch, useAppSelector } from "app/store"
|
||||
import { SharingSettings } from "app/types"
|
||||
import { locales } from "i18n"
|
||||
@@ -10,6 +10,7 @@ export function DisplaySettings() {
|
||||
const language = useAppSelector(state => state.user.settings?.language)
|
||||
const scrollSpeed = useAppSelector(state => state.user.settings?.scrollSpeed)
|
||||
const showRead = useAppSelector(state => state.user.settings?.showRead)
|
||||
const scrollMarks = useAppSelector(state => state.user.settings?.scrollMarks)
|
||||
const sharingSettings = useAppSelector(state => state.user.settings?.sharingSettings)
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
@@ -37,6 +38,12 @@ export function DisplaySettings() {
|
||||
onChange={e => dispatch(changeShowRead(e.currentTarget.checked))}
|
||||
/>
|
||||
|
||||
<Switch
|
||||
label={t`In expanded view, scrolling through entries mark them as read`}
|
||||
checked={scrollMarks}
|
||||
onChange={e => dispatch(changeScrollMarks(e.currentTarget.checked))}
|
||||
/>
|
||||
|
||||
<Divider label={t`Sharing sites`} labelPosition="center" />
|
||||
|
||||
<SimpleGrid cols={2}>
|
||||
|
||||
Reference in New Issue
Block a user