forked from Archives/Athou_commafeed
add button in the header to star entry (#1025)
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
29
commafeed-client/src/components/content/header/Star.tsx
Normal file
29
commafeed-client/src/components/content/header/Star.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user