feat: latest articles widget

This commit is contained in:
kieran 2024-09-19 11:18:01 +01:00
parent 026aaed5a1
commit 8c2833684b
No known key found for this signature in database
GPG Key ID: DE71CEB3925BE941
10 changed files with 100 additions and 13 deletions

View File

@ -12,7 +12,12 @@ interface IconButtonProps {
const IconButton = ({ onClick, icon, children, className }: IconButtonProps) => { const IconButton = ({ onClick, icon, children, className }: IconButtonProps) => {
return ( return (
<button className={classNames("icon", className)} type="button" onClick={onClick}> <button
className={classNames(
"flex items-center justify-center aspect-square w-10 h-10 !p-0 !m-0 bg-gray-dark text-white",
className,
)}
onClick={onClick}>
<Icon {...icon} /> <Icon {...icon} />
{children} {children}
</button> </button>

View File

@ -0,0 +1,68 @@
import { NostrLink } from "@snort/system";
import { useState } from "react";
import { FormattedMessage } from "react-intl";
import { Link } from "react-router-dom";
import { useArticles } from "@/Feed/ArticlesFeed";
import { findTag } from "@/Utils";
import IconButton from "../Button/IconButton";
import { ProxyImg } from "../ProxyImg";
import ProfilePreview from "../User/ProfilePreview";
import { BaseWidget } from "./base";
export default function LatestArticlesWidget() {
const [idx, setIdx] = useState(0);
const articles = useArticles();
const selected = articles.at(idx);
function next(i: number) {
setIdx(x => {
x += i;
if (x < 0) {
x = articles.length - 1;
} else if (x > articles.length) {
x = 0;
}
return x;
});
}
if (!selected) return;
const link = NostrLink.fromEvent(selected);
const image = findTag(selected, "image");
const title = findTag(selected, "title");
return (
<BaseWidget title={<FormattedMessage defaultMessage="Latest Articles" />}>
<div className="flex flex-col gap-4">
<Link
to={`/${link.encode()}`}
className="relative rounded-xl overflow-hidden w-full aspect-video"
state={selected}>
{image ? (
<ProxyImg src={image} className="absolute w-full h-full object-fit" />
) : (
<div className="absolute w-full h-full object-fit bg-gray-dark"></div>
)}
<div className="absolute bottom-2 left-4 right-4 px-2 py-1 rounded-xl text-lg font-bold text-white bg-black/50">
{title}
</div>
</Link>
<div>
<ProfilePreview
pubkey={selected.pubkey}
profileImageProps={{
subHeader: <></>,
}}
actions={
<div className="flex gap-2">
<IconButton icon={{ name: "arrowFront", className: "rotate-180", size: 14 }} onClick={() => next(-1)} />
<IconButton icon={{ name: "arrowFront", size: 14 }} onClick={() => next(1)} />
</div>
}
/>
</div>
</div>
</BaseWidget>
);
}

View File

@ -11,16 +11,16 @@ export interface BaseWidgetProps {
} }
export function BaseWidget({ children, title, icon, iconClassName, contextMenu }: BaseWidgetProps) { export function BaseWidget({ children, title, icon, iconClassName, contextMenu }: BaseWidgetProps) {
return ( return (
<div className="br p bg-gray-ultradark"> <div className="b br p bg-gray-ultradark">
{title && ( {title && (
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<div className="flex gap-2 items-center text-xl text-white font-semibold mb-1"> <div className="flex gap-2 items-center text-xl text-white font-semibold mb-2">
{icon && ( {icon && (
<div className="p-2 bg-gray-dark rounded-full"> <div className="p-2 bg-gray-dark rounded-full">
<Icon name={icon} className={iconClassName} /> <Icon name={icon} className={iconClassName} />
</div> </div>
)} )}
<div>{title}</div> <div className="text-font-color">{title}</div>
</div> </div>
{contextMenu} {contextMenu}
</div> </div>

View File

@ -3,7 +3,7 @@ export enum RightColumnWidget {
TrendingNotes, TrendingNotes,
TrendingPeople, TrendingPeople,
TrendingHashtags, TrendingHashtags,
TrendingArticls, LatestArticls,
LiveStreams, LiveStreams,
InviteFriends, InviteFriends,
} }

View File

@ -1,15 +1,10 @@
import { useContext } from "react"; import { useContext } from "react";
import Note, { NoteProps } from "@/Components/Event/EventComponent";
import { useArticles } from "@/Feed/ArticlesFeed"; import { useArticles } from "@/Feed/ArticlesFeed";
import { DeckContext } from "@/Pages/Deck/DeckLayout"; import { DeckContext } from "@/Pages/Deck/DeckLayout";
import Note from "../../Components/Event/EventComponent"; export default function Articles({ noteProps }: { noteProps?: Omit<NoteProps, "data"> }) {
const options = {
longFormPreview: true,
};
export default function Articles() {
const data = useArticles(); const data = useArticles();
const deck = useContext(DeckContext); const deck = useContext(DeckContext);
@ -19,9 +14,14 @@ export default function Articles() {
<Note <Note
data={a} data={a}
key={a.id} key={a.id}
options={options} {...noteProps}
options={{
longFormPreview: true,
...noteProps?.options,
}}
onClick={ev => { onClick={ev => {
deck?.setArticle(ev); deck?.setArticle(ev);
noteProps?.onClick?.(ev);
}} }}
/> />
))} ))}

View File

@ -2,6 +2,7 @@ import classNames from "classnames";
import { FormattedMessage } from "react-intl"; import { FormattedMessage } from "react-intl";
import { RightColumnWidget } from "@/Components/RightWidgets"; import { RightColumnWidget } from "@/Components/RightWidgets";
import LatestArticlesWidget from "@/Components/RightWidgets/articles";
import { BaseWidget } from "@/Components/RightWidgets/base"; import { BaseWidget } from "@/Components/RightWidgets/base";
import InviteFriendsWidget from "@/Components/RightWidgets/invite-friends"; import InviteFriendsWidget from "@/Components/RightWidgets/invite-friends";
import MiniStreamWidget from "@/Components/RightWidgets/mini-stream"; import MiniStreamWidget from "@/Components/RightWidgets/mini-stream";
@ -23,6 +24,7 @@ export default function RightColumn() {
RightColumnWidget.InviteFriends, RightColumnWidget.InviteFriends,
//RightColumnWidget.LiveStreams, //RightColumnWidget.LiveStreams,
RightColumnWidget.TrendingNotes, RightColumnWidget.TrendingNotes,
RightColumnWidget.LatestArticls,
RightColumnWidget.TrendingPeople, RightColumnWidget.TrendingPeople,
RightColumnWidget.TrendingHashtags, RightColumnWidget.TrendingHashtags,
] ]
@ -65,6 +67,8 @@ export default function RightColumn() {
return <InviteFriendsWidget />; return <InviteFriendsWidget />;
case RightColumnWidget.LiveStreams: case RightColumnWidget.LiveStreams:
return <MiniStreamWidget />; return <MiniStreamWidget />;
case RightColumnWidget.LatestArticls:
return <LatestArticlesWidget />;
} }
}; };

View File

@ -175,6 +175,10 @@ a.ext {
-webkit-text-fill-color: transparent; -webkit-text-fill-color: transparent;
} }
.text-outline {
-webkit-text-stroke: 1px black;
}
.br { .br {
border-radius: 16px; border-radius: 16px;
} }
@ -941,6 +945,7 @@ svg.repeat {
.pb-safe-area-plus-footer { .pb-safe-area-plus-footer {
padding-bottom: calc(env(safe-area-inset-bottom) + 56px); padding-bottom: calc(env(safe-area-inset-bottom) + 56px);
} }
.sm-hide-scrollbar { .sm-hide-scrollbar {
scrollbar-width: none; scrollbar-width: none;
-ms-overflow-style: none; -ms-overflow-style: none;

View File

@ -183,6 +183,9 @@
"2ukA4d": { "2ukA4d": {
"defaultMessage": "{n} hours" "defaultMessage": "{n} hours"
}, },
"2z7Kky": {
"defaultMessage": "Latest Articles"
},
"3/onCd": { "3/onCd": {
"defaultMessage": "Replies" "defaultMessage": "Replies"
}, },

View File

@ -60,6 +60,7 @@
"2oCF7O": "Followed by friends of friends", "2oCF7O": "Followed by friends of friends",
"2raFAu": "Application-specific data", "2raFAu": "Application-specific data",
"2ukA4d": "{n} hours", "2ukA4d": "{n} hours",
"2z7Kky": "Latest Articles",
"3/onCd": "Replies", "3/onCd": "Replies",
"39AHJm": "Sign Up", "39AHJm": "Sign Up",
"3GWu6/": "User Statuses", "3GWu6/": "User Statuses",

View File

@ -28,6 +28,7 @@ module.exports = {
}, },
textColor: { textColor: {
secondary: "var(--font-secondary-color)", secondary: "var(--font-secondary-color)",
"font-color": "var(--font-color)",
}, },
spacing: { spacing: {
px: "1px", px: "1px",