add button in the header to star entry (#1025)

This commit is contained in:
Athou
2024-04-14 11:26:39 +02:00
parent 5d75885352
commit 0d081bc47e
19 changed files with 229 additions and 39 deletions

View File

@@ -1,5 +1,6 @@
import { ActionIcon, Button, type ButtonVariant, Tooltip, useMantineTheme } from "@mantine/core"
import { type ActionIconVariant } from "@mantine/core/lib/components/ActionIcon/ActionIcon"
import { Constants } from "app/constants"
import { useActionButton } from "hooks/useActionButton"
import { forwardRef, type MouseEventHandler, type ReactNode } from "react"
@@ -22,7 +23,7 @@ export const ActionButton = forwardRef<HTMLButtonElement, ActionButtonProps>((pr
const variant = props.variant ?? "subtle"
const iconOnly = (mobile && !props.showLabelOnMobile) || (!mobile && props.hideLabelOnDesktop)
return iconOnly ? (
<Tooltip label={props.label} openDelay={500}>
<Tooltip label={props.label} openDelay={Constants.tooltip.delay}>
<ActionIcon ref={ref} color={theme.primaryColor} variant={variant} className={props.className} onClick={props.onClick}>
{props.icon}
</ActionIcon>

View File

@@ -1,5 +1,6 @@
import { Trans } from "@lingui/macro"
import { Tooltip } from "@mantine/core"
import { Constants } from "app/constants"
import dayjs from "dayjs"
import { useEffect, useState } from "react"
@@ -13,7 +14,7 @@ export function RelativeDate(props: { date: Date | number | undefined }) {
if (!props.date) return <Trans>N/A</Trans>
const date = dayjs(props.date)
return (
<Tooltip label={date.toDate().toLocaleString()} openDelay={500}>
<Tooltip label={date.toDate().toLocaleString()} openDelay={Constants.tooltip.delay}>
<span>{date.from(dayjs(now))}</span>
</Tooltip>
)

View File

@@ -1,8 +1,10 @@
import { Box, Divider, type MantineRadius, type MantineSpacing, Paper } from "@mantine/core"
import { Constants } from "app/constants"
import { useAppSelector } from "app/store"
import { type Entry, type ViewMode } from "app/types"
import { FeedEntryCompactHeader } from "components/content/header/FeedEntryCompactHeader"
import { FeedEntryHeader } from "components/content/header/FeedEntryHeader"
import { useMobile } from "hooks/useMobile"
import { useViewMode } from "hooks/useViewMode"
import React from "react"
import { useSwipeable } from "react-swipeable"
@@ -103,6 +105,15 @@ export function FeedEntry(props: FeedEntryProps) {
maxWidth: props.maxWidth,
})
const externalLinkDisplayMode = useAppSelector(state => state.user.settings?.externalLinkIconDisplayMode)
const starIconDisplayMode = useAppSelector(state => state.user.settings?.starIconDisplayMode)
const mobile = useMobile()
const showExternalLinkIcon =
externalLinkDisplayMode && ["always", mobile ? "on_mobile" : "on_desktop"].includes(externalLinkDisplayMode)
const showStarIcon =
props.entry.markable && starIconDisplayMode && ["always", mobile ? "on_mobile" : "on_desktop"].includes(starIconDisplayMode)
const swipeHandlers = useSwipeable({
onSwipedLeft: props.onSwipedLeft,
})
@@ -147,8 +158,21 @@ export function FeedEntry(props: FeedEntryProps) {
onContextMenu={props.onHeaderRightClick}
>
<Box px={paddingX} py={paddingY} {...swipeHandlers}>
{compactHeader && <FeedEntryCompactHeader entry={props.entry} />}
{!compactHeader && <FeedEntryHeader entry={props.entry} expanded={props.expanded} />}
{compactHeader && (
<FeedEntryCompactHeader
entry={props.entry}
showStarIcon={showStarIcon}
showExternalLinkIcon={showExternalLinkIcon}
/>
)}
{!compactHeader && (
<FeedEntryHeader
entry={props.entry}
expanded={props.expanded}
showStarIcon={showStarIcon}
showExternalLinkIcon={showExternalLinkIcon}
/>
)}
</Box>
</a>
{props.expanded && (

View File

@@ -2,6 +2,7 @@ import { Box, Text } from "@mantine/core"
import { type Entry } from "app/types"
import { FeedFavicon } from "components/content/FeedFavicon"
import { OpenExternalLink } from "components/content/header/OpenExternalLink"
import { Star } from "components/content/header/Star"
import { RelativeDate } from "components/RelativeDate"
import { OnDesktop } from "components/responsive/OnDesktop"
import { tss } from "tss"
@@ -9,6 +10,8 @@ import { FeedEntryTitle } from "./FeedEntryTitle"
export interface FeedEntryHeaderProps {
entry: Entry
showStarIcon?: boolean
showExternalLinkIcon?: boolean
}
const useStyles = tss
@@ -46,6 +49,7 @@ export function FeedEntryCompactHeader(props: FeedEntryHeaderProps) {
})
return (
<Box className={classes.wrapper}>
{props.showStarIcon && <Star entry={props.entry} />}
<Box>
<FeedFavicon url={props.entry.iconUrl} />
</Box>
@@ -62,7 +66,7 @@ export function FeedEntryCompactHeader(props: FeedEntryHeaderProps) {
<RelativeDate date={props.entry.date} />
</Text>
</OnDesktop>
<OpenExternalLink entry={props.entry} />
{props.showExternalLinkIcon && <OpenExternalLink entry={props.entry} />}
</Box>
)
}

View File

@@ -1,7 +1,8 @@
import { Box, Space, Text } from "@mantine/core"
import { Box, Flex, Space, Text } from "@mantine/core"
import { type Entry } from "app/types"
import { FeedFavicon } from "components/content/FeedFavicon"
import { OpenExternalLink } from "components/content/header/OpenExternalLink"
import { Star } from "components/content/header/Star"
import { RelativeDate } from "components/RelativeDate"
import { tss } from "tss"
import { FeedEntryTitle } from "./FeedEntryTitle"
@@ -9,6 +10,8 @@ import { FeedEntryTitle } from "./FeedEntryTitle"
export interface FeedEntryHeaderProps {
entry: Entry
expanded: boolean
showStarIcon?: boolean
showExternalLinkIcon?: boolean
}
const useStyles = tss
@@ -17,16 +20,9 @@ const useStyles = tss
}>()
.create(({ colorScheme, read }) => ({
main: {
display: "flex",
alignItems: "flex-start",
justifyContent: "space-between",
},
mainText: {
fontWeight: colorScheme === "light" && !read ? "bold" : "inherit",
},
details: {
display: "flex",
alignItems: "center",
fontSize: "90%",
},
}))
@@ -37,13 +33,18 @@ export function FeedEntryHeader(props: FeedEntryHeaderProps) {
})
return (
<Box>
<Box className={classes.main}>
<Box className={classes.mainText}>
<Flex align="flex-start" justify="space-between">
<Flex align="flex-start" className={classes.main}>
{props.showStarIcon && (
<Box ml={-6}>
<Star entry={props.entry} />
</Box>
)}
<FeedEntryTitle entry={props.entry} />
</Box>
<OpenExternalLink entry={props.entry} />
</Box>
<Box className={classes.details}>
</Flex>
{props.showExternalLinkIcon && <OpenExternalLink entry={props.entry} />}
</Flex>
<Flex align="center" className={classes.details}>
<FeedFavicon url={props.entry.iconUrl} />
<Space w={6} />
<Text c="dimmed">
@@ -51,7 +52,7 @@ export function FeedEntryHeader(props: FeedEntryHeaderProps) {
<span> · </span>
<RelativeDate date={props.entry.date} />
</Text>
</Box>
</Flex>
{props.expanded && (
<Box className={classes.details}>
<Text c="dimmed">

View File

@@ -1,5 +1,6 @@
import { Trans } from "@lingui/macro"
import { ActionIcon, Anchor, Tooltip } from "@mantine/core"
import { Constants } from "app/constants"
import { markEntry } from "app/entries/thunks"
import { useAppDispatch } from "app/store"
import { type Entry } from "app/types"
@@ -19,9 +20,9 @@ export function OpenExternalLink(props: { entry: Entry }) {
return (
<Anchor href={props.entry.url} target="_blank" rel="noreferrer" onClick={onClick}>
<Tooltip label={<Trans>Open link</Trans>}>
<Tooltip label={<Trans>Open link</Trans>} openDelay={Constants.tooltip.delay}>
<ActionIcon variant="transparent" c="dimmed">
<TbExternalLink />
<TbExternalLink size={18} />
</ActionIcon>
</Tooltip>
</Anchor>

View File

@@ -0,0 +1,29 @@
import { Trans } from "@lingui/macro"
import { ActionIcon, Tooltip } from "@mantine/core"
import { Constants } from "app/constants"
import { starEntry } from "app/entries/thunks"
import { useAppDispatch } from "app/store"
import type { Entry } from "app/types"
import { TbStar, TbStarFilled } from "react-icons/tb"
export function Star(props: { entry: Entry }) {
const dispatch = useAppDispatch()
const onClick = (e: React.MouseEvent) => {
e.stopPropagation()
e.preventDefault()
dispatch(
starEntry({
entry: props.entry,
starred: !props.entry.starred,
})
)
}
return (
<Tooltip label={props.entry.starred ? <Trans>Unstar</Trans> : <Trans>Star</Trans>} openDelay={Constants.tooltip.delay}>
<ActionIcon variant="transparent" onClick={onClick}>
{props.entry.starred ? <TbStarFilled size={18} /> : <TbStar size={18} />}
</ActionIcon>
</Tooltip>
)
}

View File

@@ -1,10 +1,12 @@
import { Trans } from "@lingui/macro"
import { t, Trans } from "@lingui/macro"
import { Divider, Group, Radio, Select, SimpleGrid, Stack, Switch } from "@mantine/core"
import { type ComboboxData } from "@mantine/core/lib/components/Combobox/Combobox.types"
import { Constants } from "app/constants"
import { useAppDispatch, useAppSelector } from "app/store"
import { type ScrollMode, type SharingSettings } from "app/types"
import { type IconDisplayMode, type ScrollMode, type SharingSettings } from "app/types"
import {
changeCustomContextMenu,
changeExternalLinkIconDisplayMode,
changeLanguage,
changeMarkAllAsReadConfirmation,
changeMobileFooter,
@@ -13,16 +15,38 @@ import {
changeScrollSpeed,
changeSharingSetting,
changeShowRead,
changeStarIconDisplayMode,
} from "app/user/thunks"
import { locales } from "i18n"
import { type ReactNode } from "react"
const displayModeData: ComboboxData = [
{
value: "always",
label: t`Always`,
},
{
value: "on_desktop",
label: t`On desktop`,
},
{
value: "on_mobile",
label: t`On mobile`,
},
{
value: "never",
label: t`Never`,
},
]
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 scrollMode = useAppSelector(state => state.user.settings?.scrollMode)
const starIconDisplayMode = useAppSelector(state => state.user.settings?.starIconDisplayMode)
const externalLinkIconDisplayMode = useAppSelector(state => state.user.settings?.externalLinkIconDisplayMode)
const markAllAsReadConfirmation = useAppSelector(state => state.user.settings?.markAllAsReadConfirmation)
const customContextMenu = useAppSelector(state => state.user.settings?.customContextMenu)
const mobileFooter = useAppSelector(state => state.user.settings?.mobileFooter)
@@ -59,18 +83,34 @@ export function DisplaySettings() {
onChange={async e => await dispatch(changeMarkAllAsReadConfirmation(e.currentTarget.checked))}
/>
<Switch
label={<Trans>Show CommaFeed's own context menu on right click</Trans>}
checked={customContextMenu}
onChange={async e => await dispatch(changeCustomContextMenu(e.currentTarget.checked))}
/>
<Switch
label={<Trans>On mobile, show action buttons at the bottom of the screen</Trans>}
checked={mobileFooter}
onChange={async e => await dispatch(changeMobileFooter(e.currentTarget.checked))}
/>
<Divider label={<Trans>Entry headers</Trans>} labelPosition="center" />
<Select
description={<Trans>Show star icon</Trans>}
value={starIconDisplayMode}
data={displayModeData}
onChange={async s => await dispatch(changeStarIconDisplayMode(s as IconDisplayMode))}
/>
<Select
description={<Trans>Show external link icon</Trans>}
value={externalLinkIconDisplayMode}
data={displayModeData}
onChange={async s => await dispatch(changeExternalLinkIconDisplayMode(s as IconDisplayMode))}
/>
<Switch
label={<Trans>Show CommaFeed's own context menu on right click</Trans>}
checked={customContextMenu}
onChange={async e => await dispatch(changeCustomContextMenu(e.currentTarget.checked))}
/>
<Divider label={<Trans>Scrolling</Trans>} labelPosition="center" />
<Radio.Group

View File

@@ -1,4 +1,5 @@
import { Badge, Tooltip } from "@mantine/core"
import { Constants } from "app/constants"
import { tss } from "tss"
const useStyles = tss.create(() => ({
@@ -16,7 +17,7 @@ export function UnreadCount(props: { unreadCount: number }) {
const count = props.unreadCount >= 10000 ? "10k+" : props.unreadCount
return (
<Tooltip label={props.unreadCount} disabled={props.unreadCount === count}>
<Tooltip label={props.unreadCount} disabled={props.unreadCount === count} openDelay={Constants.tooltip.delay}>
<Badge className={classes.badge} variant="light">
{count}
</Badge>