mirror of
https://github.com/Athou/commafeed.git
synced 2026-03-21 21:37:29 +00:00
add initial support for expanded mode
This commit is contained in:
@@ -35,7 +35,7 @@
|
|||||||
"react-hooks/exhaustive-deps": [
|
"react-hooks/exhaustive-deps": [
|
||||||
"warn",
|
"warn",
|
||||||
{
|
{
|
||||||
"additionalHooks": "(^useAsync$)"
|
"additionalHooks": "(^useAsync$|useDidUpdate)"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { showNotification } from "@mantine/notifications"
|
|||||||
import { createAsyncThunk, createSlice, isAnyOf } from "@reduxjs/toolkit"
|
import { createAsyncThunk, createSlice, isAnyOf } from "@reduxjs/toolkit"
|
||||||
import { client } from "app/client"
|
import { client } from "app/client"
|
||||||
import { RootState } from "app/store"
|
import { RootState } from "app/store"
|
||||||
import { ReadingMode, ReadingOrder, Settings, SharingSettings, UserModel } from "app/types"
|
import { ReadingMode, ReadingOrder, Settings, SharingSettings, UserModel, ViewMode } from "app/types"
|
||||||
import { reloadEntries } from "./entries"
|
import { reloadEntries } from "./entries"
|
||||||
|
|
||||||
interface UserState {
|
interface UserState {
|
||||||
@@ -33,6 +33,12 @@ export const changeReadingOrder = createAsyncThunk<void, ReadingOrder, { state:
|
|||||||
thunkApi.dispatch(reloadEntries())
|
thunkApi.dispatch(reloadEntries())
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
export const changeViewMode = createAsyncThunk<void, ViewMode, { state: RootState }>("settings/viewMode", (viewMode, thunkApi) => {
|
||||||
|
const { settings } = thunkApi.getState().user
|
||||||
|
if (!settings) return
|
||||||
|
client.user.saveSettings({ ...settings, viewMode })
|
||||||
|
thunkApi.dispatch(reloadEntries())
|
||||||
|
})
|
||||||
export const changeLanguage = createAsyncThunk<void, string, { state: RootState }>("settings/language", (language, thunkApi) => {
|
export const changeLanguage = createAsyncThunk<void, string, { state: RootState }>("settings/language", (language, thunkApi) => {
|
||||||
const { settings } = thunkApi.getState().user
|
const { settings } = thunkApi.getState().user
|
||||||
if (!settings) return
|
if (!settings) return
|
||||||
@@ -82,6 +88,10 @@ export const userSlice = createSlice({
|
|||||||
if (!state.settings) return
|
if (!state.settings) return
|
||||||
state.settings.readingOrder = action.meta.arg
|
state.settings.readingOrder = action.meta.arg
|
||||||
})
|
})
|
||||||
|
builder.addCase(changeViewMode.pending, (state, action) => {
|
||||||
|
if (!state.settings) return
|
||||||
|
state.settings.viewMode = action.meta.arg
|
||||||
|
})
|
||||||
builder.addCase(changeLanguage.pending, (state, action) => {
|
builder.addCase(changeLanguage.pending, (state, action) => {
|
||||||
if (!state.settings) return
|
if (!state.settings) return
|
||||||
state.settings.language = action.meta.arg
|
state.settings.language = action.meta.arg
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export function FeedEntries() {
|
|||||||
const entriesTimestamp = useAppSelector(state => state.entries.timestamp)
|
const entriesTimestamp = useAppSelector(state => state.entries.timestamp)
|
||||||
const selectedEntryId = useAppSelector(state => state.entries.selectedEntryId)
|
const selectedEntryId = useAppSelector(state => state.entries.selectedEntryId)
|
||||||
const hasMore = useAppSelector(state => state.entries.hasMore)
|
const hasMore = useAppSelector(state => state.entries.hasMore)
|
||||||
|
const viewMode = useAppSelector(state => state.user.settings?.viewMode)
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
const selectedEntry = entries.find(e => e.id === selectedEntryId)
|
const selectedEntry = entries.find(e => e.id === selectedEntryId)
|
||||||
@@ -151,7 +152,7 @@ export function FeedEntries() {
|
|||||||
refs.current[e.id] = el!
|
refs.current[e.id] = el!
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FeedEntry entry={e} expanded={!!e.expanded} />
|
<FeedEntry entry={e} expanded={!!e.expanded || viewMode === "expanded"} />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</InfiniteScroll>
|
</InfiniteScroll>
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Anchor, Box, createStyles, Divider, Paper } from "@mantine/core"
|
import { Anchor, Box, createStyles, Divider, Paper } from "@mantine/core"
|
||||||
|
import { useDidUpdate } from "@mantine/hooks"
|
||||||
import { Constants } from "app/constants"
|
import { Constants } from "app/constants"
|
||||||
import { markEntry, selectEntry } from "app/slices/entries"
|
import { markEntry, selectEntry } from "app/slices/entries"
|
||||||
import { useAppDispatch, useAppSelector } from "app/store"
|
import { useAppDispatch, useAppSelector } from "app/store"
|
||||||
import { Entry } from "app/types"
|
import { Entry } from "app/types"
|
||||||
import React, { useEffect, useRef } from "react"
|
import React, { useRef } from "react"
|
||||||
import { FeedEntryBody } from "./FeedEntryBody"
|
import { FeedEntryBody } from "./FeedEntryBody"
|
||||||
import { FeedEntryFooter } from "./FeedEntryFooter"
|
import { FeedEntryFooter } from "./FeedEntryFooter"
|
||||||
import { FeedEntryHeader } from "./FeedEntryHeader"
|
import { FeedEntryHeader } from "./FeedEntryHeader"
|
||||||
@@ -53,8 +54,9 @@ export function FeedEntry(props: FeedEntryProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// scroll to entry when expanded
|
// scroll to entry when expanded
|
||||||
|
// we use useDidUpdate to avoid scrolling towards all entries during initial load when viewMode is "expanded"
|
||||||
const ref = useRef<HTMLDivElement>(null)
|
const ref = useRef<HTMLDivElement>(null)
|
||||||
useEffect(() => {
|
useDidUpdate(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!ref.current) return
|
if (!ref.current) return
|
||||||
if (!props.expanded) return
|
if (!props.expanded) return
|
||||||
|
|||||||
@@ -1,16 +1,48 @@
|
|||||||
import { Trans } from "@lingui/macro"
|
import { Trans } from "@lingui/macro"
|
||||||
import { Divider, Menu, useMantineColorScheme } from "@mantine/core"
|
import { Box, Divider, Group, Menu, SegmentedControl, SegmentedControlItem, useMantineColorScheme } from "@mantine/core"
|
||||||
import { redirectToAbout, redirectToAdminUsers, redirectToMetrics, redirectToSettings } from "app/slices/redirect"
|
import { redirectToAbout, redirectToAdminUsers, redirectToMetrics, redirectToSettings } from "app/slices/redirect"
|
||||||
|
import { changeViewMode } from "app/slices/user"
|
||||||
import { useAppDispatch, useAppSelector } from "app/store"
|
import { useAppDispatch, useAppSelector } from "app/store"
|
||||||
|
import { ViewMode } from "app/types"
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import { TbChartLine, TbHelp, TbMoon, TbPower, TbSettings, TbSun, TbUsers } from "react-icons/tb"
|
import { TbChartLine, TbHelp, TbList, TbMoon, TbNotes, TbPower, TbSettings, TbSun, TbUsers } from "react-icons/tb"
|
||||||
|
|
||||||
interface ProfileMenuProps {
|
interface ProfileMenuProps {
|
||||||
control: React.ReactElement
|
control: React.ReactElement
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ViewModeControlItem extends SegmentedControlItem {
|
||||||
|
value: ViewMode
|
||||||
|
}
|
||||||
|
|
||||||
|
const viewModeData: ViewModeControlItem[] = [
|
||||||
|
{
|
||||||
|
value: "title",
|
||||||
|
label: (
|
||||||
|
<Group>
|
||||||
|
<TbList />
|
||||||
|
<Box ml={6}>
|
||||||
|
<Trans>Compact</Trans>
|
||||||
|
</Box>
|
||||||
|
</Group>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "expanded",
|
||||||
|
label: (
|
||||||
|
<Group>
|
||||||
|
<TbNotes />
|
||||||
|
<Box ml={6}>
|
||||||
|
<Trans>Expanded</Trans>
|
||||||
|
</Box>
|
||||||
|
</Group>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
export function ProfileMenu(props: ProfileMenuProps) {
|
export function ProfileMenu(props: ProfileMenuProps) {
|
||||||
const [opened, setOpened] = useState(false)
|
const [opened, setOpened] = useState(false)
|
||||||
|
const viewMode = useAppSelector(state => state.user.settings?.viewMode)
|
||||||
const admin = useAppSelector(state => state.user.profile?.admin)
|
const admin = useAppSelector(state => state.user.profile?.admin)
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const { colorScheme, toggleColorScheme } = useMantineColorScheme()
|
const { colorScheme, toggleColorScheme } = useMantineColorScheme()
|
||||||
@@ -33,9 +65,22 @@ export function ProfileMenu(props: ProfileMenuProps) {
|
|||||||
>
|
>
|
||||||
<Trans>Settings</Trans>
|
<Trans>Settings</Trans>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
<Menu.Label>
|
||||||
|
<Trans>Display</Trans>
|
||||||
|
</Menu.Label>
|
||||||
<Menu.Item icon={dark ? <TbMoon /> : <TbSun />} onClick={() => toggleColorScheme()}>
|
<Menu.Item icon={dark ? <TbMoon /> : <TbSun />} onClick={() => toggleColorScheme()}>
|
||||||
<Trans>Theme</Trans>
|
<Trans>Theme</Trans>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
<SegmentedControl
|
||||||
|
fullWidth
|
||||||
|
orientation="vertical"
|
||||||
|
data={viewModeData}
|
||||||
|
value={viewMode}
|
||||||
|
onChange={e => dispatch(changeViewMode(e as ViewMode))}
|
||||||
|
mb="xs"
|
||||||
|
/>
|
||||||
|
|
||||||
{admin && (
|
{admin && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -161,6 +161,10 @@ msgstr "CommaFeed next unread item"
|
|||||||
msgid "CommaFeed version {version} ({revision})"
|
msgid "CommaFeed version {version} ({revision})"
|
||||||
msgstr "CommaFeed version {version} ({revision})"
|
msgstr "CommaFeed version {version} ({revision})"
|
||||||
|
|
||||||
|
#: src/components/header/ProfileMenu.tsx
|
||||||
|
msgid "Compact"
|
||||||
|
msgstr "Compact"
|
||||||
|
|
||||||
#: src/components/header/MarkAllAsReadButton.tsx
|
#: src/components/header/MarkAllAsReadButton.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
@@ -202,6 +206,7 @@ msgstr "Delete user"
|
|||||||
msgid "Desc"
|
msgid "Desc"
|
||||||
msgstr "Desc"
|
msgstr "Desc"
|
||||||
|
|
||||||
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/pages/app/SettingsPage.tsx
|
#: src/pages/app/SettingsPage.tsx
|
||||||
msgid "Display"
|
msgid "Display"
|
||||||
msgstr "Display"
|
msgstr "Display"
|
||||||
@@ -252,6 +257,10 @@ msgstr "Error"
|
|||||||
msgid "Example: {example}."
|
msgid "Example: {example}."
|
||||||
msgstr "Example: {example}."
|
msgstr "Example: {example}."
|
||||||
|
|
||||||
|
#: src/components/header/ProfileMenu.tsx
|
||||||
|
msgid "Expanded"
|
||||||
|
msgstr "Expanded"
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
|
msgid "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
|
||||||
msgstr "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
|
msgstr "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
|
||||||
|
|||||||
@@ -161,6 +161,10 @@ msgstr "CommaFeed prochain article non lu"
|
|||||||
msgid "CommaFeed version {version} ({revision})"
|
msgid "CommaFeed version {version} ({revision})"
|
||||||
msgstr "CommaFeed version {version} ({revision})"
|
msgstr "CommaFeed version {version} ({revision})"
|
||||||
|
|
||||||
|
#: src/components/header/ProfileMenu.tsx
|
||||||
|
msgid "Compact"
|
||||||
|
msgstr "Compact"
|
||||||
|
|
||||||
#: src/components/header/MarkAllAsReadButton.tsx
|
#: src/components/header/MarkAllAsReadButton.tsx
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
@@ -202,6 +206,7 @@ msgstr "Effacer l'utilisateur"
|
|||||||
msgid "Desc"
|
msgid "Desc"
|
||||||
msgstr "Descendant"
|
msgstr "Descendant"
|
||||||
|
|
||||||
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/pages/app/SettingsPage.tsx
|
#: src/pages/app/SettingsPage.tsx
|
||||||
msgid "Display"
|
msgid "Display"
|
||||||
msgstr "Affichage"
|
msgstr "Affichage"
|
||||||
@@ -252,6 +257,10 @@ msgstr "Erreur"
|
|||||||
msgid "Example: {example}."
|
msgid "Example: {example}."
|
||||||
msgstr "Exemple : {example}."
|
msgstr "Exemple : {example}."
|
||||||
|
|
||||||
|
#: src/components/header/ProfileMenu.tsx
|
||||||
|
msgid "Expanded"
|
||||||
|
msgstr "Vue étendue"
|
||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
|
msgid "Export your subscriptions and categories as an OPML file that can be imported in other feed reading services"
|
||||||
msgstr "Exporter vos abonnements et catégories en tant que fichier OPML qui peut être importé dans d'autres services de lecture de flux"
|
msgstr "Exporter vos abonnements et catégories en tant que fichier OPML qui peut être importé dans d'autres services de lecture de flux"
|
||||||
|
|||||||
Reference in New Issue
Block a user