import { ActionIcon, Badge, Card, Center, Flex, Group, Image, Loader, MediaQuery, ScrollArea, Stack, Text, Title, createStyles, } from '@mantine/core'; import { IconClock, IconRefresh, IconRss } from '@tabler/icons-react'; import dayjs from 'dayjs'; import { useTranslation } from 'next-i18next'; import Link from 'next/link'; import { useConfigContext } from '~/config/provider'; import { api } from '~/utils/api'; import { defineWidget } from '../helper'; import { IWidget } from '../widgets'; const definition = defineWidget({ id: 'rss', icon: IconRss, options: { rssFeedUrl: { type: 'multiple-text', defaultValue: [], }, refreshInterval: { type: 'slider', defaultValue: 30, min: 15, max: 300, step: 15, }, dangerousAllowSanitizedItemContent: { type: 'switch', defaultValue: false, }, textLinesClamp: { type: 'slider', defaultValue: 5, min: 1, max: 50, step: 1, }, }, gridstack: { minWidth: 2, minHeight: 2, maxWidth: 12, maxHeight: 12, }, component: RssTile, }); export type IRssWidget = IWidget<(typeof definition)['id'], typeof definition>; interface RssTileProps { widget: IRssWidget; } function RssTile({ widget }: RssTileProps) { const { t } = useTranslation('modules/rss'); const { name: configName } = useConfigContext(); const { data, isLoading, isFetching, isError, refetch } = useGetRssFeeds( configName, widget.properties.rssFeedUrl, widget.properties.refreshInterval, widget.id ); const { classes } = useStyles(); function formatDate(input: string): string { // Parse the input date as a local date try { const inputDate = dayjs(new Date(input)); const now = dayjs(); // Current date and time const difference = now.diff(inputDate, 'ms'); const duration = dayjs.duration(difference, 'ms'); const humanizedDuration = duration.humanize(); return `${humanizedDuration} ago`; } catch (e) { return 'Error'; } } if (!data || isLoading) { return (
); } if (data.length < 1 || !data[0].feed || isError) { return (
{t('descriptor.card.errors.general.title')} {t('descriptor.card.errors.general.text')}
); } return ( {data.map((feed, index) => ( {feed.feed && feed.feed.items.map((item: any, index: number) => ( {item.enclosure && ( // eslint-disable-next-line @next/next/no-img-element backdrop )} {item.enclosure && item.enclosure.url && ( )} {item.categories && ( {item.categories.map((category: any, categoryIndex: number) => ( {category} ))} )} {item.title} {item.pubDate && ( )} ))} ))} ); } export const useGetRssFeeds = ( configName: string | undefined, feedUrls: string[], refreshInterval: number, widgetId: string ) => api.rss.all.useQuery( { configName: configName ?? '', feedUrls, widgetId, }, { // Cache the results for 24 hours cacheTime: 1000 * 60 * 60 * 24, staleTime: 1000 * 60 * refreshInterval, enabled: !!configName, } ); interface RefetchButtonProps { refetch: () => void; isFetching: boolean; } const RefetchButton = ({ isFetching, refetch }: RefetchButtonProps) => ( refetch()} bottom={10} styles={{ root: { borderColor: 'red', }, }} > {isFetching ? : } ); const InfoDisplay = ({ date, title }: { date: string; title: string | undefined }) => ( {date} {title && ( {title} )} ); const useStyles = createStyles(({ colorScheme, colors, radius, spacing }) => ({ backgroundImage: { position: 'absolute', width: '100%', height: '100%', filter: colorScheme === 'dark' ? 'blur(30px)' : 'blur(15px)', transform: 'scaleX(-1)', opacity: colorScheme === 'dark' ? 0.3 : 0.2, transition: 'ease-in-out 0.2s', '&:hover': { opacity: colorScheme === 'dark' ? 0.4 : 0.3, filter: 'blur(40px) brightness(0.7)', }, }, itemContent: { img: { height: 100, width: 'auto', borderRadius: radius.sm, }, blockquote: { marginLeft: 10, marginRight: 10, paddingLeft: spacing.xs, paddingRight: spacing.xs, paddingTop: 1, paddingBottom: 1, borderLeftWidth: 4, borderLeftStyle: 'solid', borderLeftColor: colors.red[5], borderRadius: radius.sm, backgroundColor: colorScheme === 'dark' ? colors.dark[4] : '', }, }, })); export default definition;