From 913a352deefe67218556a457100c78ee624f5eca Mon Sep 17 00:00:00 2001 From: Lim Chee Aun <cheeaun@gmail.com> Date: Thu, 6 Apr 2023 01:14:38 +0800 Subject: [PATCH] Add Trending page --- src/app.jsx | 2 + src/components/icon.jsx | 1 + src/components/menu.jsx | 3 + src/pages/trending.jsx | 127 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 133 insertions(+) create mode 100644 src/pages/trending.jsx diff --git a/src/app.jsx b/src/app.jsx index 8e4001aa..391e2e45 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -41,6 +41,7 @@ import Public from './pages/public'; import Search from './pages/search'; import Settings from './pages/settings'; import Status from './pages/status'; +import Trending from './pages/trending'; import Welcome from './pages/welcome'; import { api, @@ -238,6 +239,7 @@ function App() { <Route index element={<Public />} /> <Route path="l" element={<Public local />} /> </Route> + <Route path="/:instance?/trending" element={<Trending />} /> <Route path="/:instance?/search" element={<Search />} /> {/* <Route path="/:anything" element={<NotFound />} /> */} </Routes> diff --git a/src/components/icon.jsx b/src/components/icon.jsx index afc6e906..0d37deb6 100644 --- a/src/components/icon.jsx +++ b/src/components/icon.jsx @@ -75,6 +75,7 @@ const ICONS = { refresh: 'mingcute:refresh-2-line', emoji2: 'mingcute:emoji-2-line', filter: 'mingcute:filter-2-line', + chart: 'mingcute:chart-line-line', }; const modules = import.meta.glob('/node_modules/@iconify-icons/mingcute/*.js'); diff --git a/src/components/menu.jsx b/src/components/menu.jsx index 7e36ca83..363e5219 100644 --- a/src/components/menu.jsx +++ b/src/components/menu.jsx @@ -137,6 +137,9 @@ function NavMenu(props) { <MenuLink to={`/${instance}/p`}> <Icon icon="earth" size="l" /> <span>Federated</span> </MenuLink> + <MenuLink to={`/${instance}/trending`}> + <Icon icon="chart" size="l" /> <span>Trending</span> + </MenuLink> {authenticated && ( <> <MenuDivider /> diff --git a/src/pages/trending.jsx b/src/pages/trending.jsx new file mode 100644 index 00000000..c92a7e11 --- /dev/null +++ b/src/pages/trending.jsx @@ -0,0 +1,127 @@ +import { Menu, MenuItem } from '@szhsin/react-menu'; +import { useRef } from 'preact/hooks'; +import { useNavigate, useParams } from 'react-router-dom'; +import { useSnapshot } from 'valtio'; + +import Icon from '../components/icon'; +import Timeline from '../components/timeline'; +import { api } from '../utils/api'; +import { filteredItems } from '../utils/filters'; +import states from '../utils/states'; +import { saveStatus } from '../utils/states'; +import useTitle from '../utils/useTitle'; + +const LIMIT = 20; + +function Trending(props) { + const snapStates = useSnapshot(states); + const params = useParams(); + const { masto, instance } = api({ + instance: props?.instance || params.instance, + }); + const title = `Trending (${instance})`; + useTitle(title, `/:instance?/trending`); + const navigate = useNavigate(); + const latestItem = useRef(); + + const trendIterator = useRef(); + async function fetchTrend(firstLoad) { + if (firstLoad || !trendIterator.current) { + trendIterator.current = masto.v1.trends.listStatuses({ + limit: LIMIT, + }); + } + const results = await trendIterator.current.next(); + let { value } = results; + if (value?.length) { + if (firstLoad) { + latestItem.current = value[0].id; + } + + value = filteredItems(value, 'public'); // Might not work here + value.forEach((item) => { + saveStatus(item, instance); + }); + } + return results; + } + + async function checkForUpdates() { + try { + const results = await masto.v1.trends + .listStatuses({ + limit: 1, + since_id: latestItem.current, + }) + .next(); + let { value } = results; + value = filteredItems(value, 'public'); + if (value?.length) { + return true; + } + return false; + } catch (e) { + return false; + } + } + + return ( + <Timeline + key={instance} + title={title} + titleComponent={ + <h1 class="header-account"> + <b>Trending</b> + <div>{instance}</div> + </h1> + } + id="trending" + instance={instance} + emptyText="No trending posts." + errorText="Unable to load posts" + fetchItems={fetchTrend} + checkForUpdates={checkForUpdates} + useItemID + headerStart={<></>} + boostsCarousel={snapStates.settings.boostsCarousel} + allowFilters + headerEnd={ + <Menu + portal={{ + target: document.body, + }} + // setDownOverflow + overflow="auto" + viewScroll="close" + position="anchor" + boundingBoxPadding="8 8 8 8" + menuButton={ + <button type="button" class="plain"> + <Icon icon="more" size="l" /> + </button> + } + > + <MenuItem + onClick={() => { + let newInstance = prompt( + 'Enter a new instance e.g. "mastodon.social"', + ); + if (!/\./.test(newInstance)) { + if (newInstance) alert('Invalid instance'); + return; + } + if (newInstance) { + newInstance = newInstance.toLowerCase().trim(); + navigate(`/${newInstance}/trending`); + } + }} + > + <Icon icon="bus" /> <span>Go to another instance…</span> + </MenuItem> + </Menu> + } + /> + ); +} + +export default Trending;