Skip to content

Commit

Permalink
feat: Add adjacent feeds feature (Closes #202) (#293)
Browse files Browse the repository at this point in the history
* style: Add placeholder div

style: Fixed FeedCard width

fix: Can't delete cache

chore: Adding clear adjacent feed cache

refactor: Adjacent feeds API

feat: Adding some cache function

style: Change flex direction on mobile screens

feat: Adding adjacent feeds to the frontend

feat: Adding adjacent feeds API to the backend

* style: 调整上一篇/下一篇样式

Signed-off-by: Xeu <[email protected]>

---------

Signed-off-by: Xeu <[email protected]>
Co-authored-by: Sittymin <[email protected]>
  • Loading branch information
OXeu and Sittymin authored Dec 13, 2024
1 parent 37205bc commit 13b077d
Show file tree
Hide file tree
Showing 12 changed files with 304 additions and 55 deletions.
3 changes: 2 additions & 1 deletion client/public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,9 @@
}
},
"logout": "Logout",
"main_content": "Main content",
"next": "Next Page",
"no_more": "No More",
"preview": "Preview",
"previous": "Previous Page",
"publish": {
Expand Down Expand Up @@ -213,7 +215,6 @@
"title": "Top"
},
"unlisted": "Unlisted",
"untitled": "Untitled",
"untop": {
"title": "Untop"
},
Expand Down
3 changes: 2 additions & 1 deletion client/public/locales/ja/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,9 @@
}
},
"logout": "ログアウト",
"main_content": "本文",
"next": "次のページ",
"no_more": "もうない",
"preview": "プレビュー",
"previous": "前のページ",
"publish": {
Expand Down Expand Up @@ -213,7 +215,6 @@
"title": "キャッシュをクリア"
},
"unlisted": "リストされていない",
"untitled": "無題",
"untop": {
"title": "トップ解除"
},
Expand Down
3 changes: 2 additions & 1 deletion client/public/locales/zh-CN/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,9 @@
}
},
"logout": "退出登录",
"main_content": "正文",
"next": "下一页",
"no_more": "没有更多了",
"preview": "预览",
"previous": "上一页",
"publish": {
Expand Down Expand Up @@ -213,7 +215,6 @@
"title": "置顶"
},
"unlisted": "未列出",
"untitled": "未列出",
"untop": {
"title": "取消置顶"
},
Expand Down
3 changes: 2 additions & 1 deletion client/public/locales/zh-TW/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,9 @@
}
},
"logout": "登出",
"main_content": "正文",
"next": "下一頁",
"no_more": "沒有更多了",
"preview": "預覽",
"previous": "上一頁",
"publish": {
Expand Down Expand Up @@ -213,7 +215,6 @@
"title": "置頂"
},
"unlisted": "未列出",
"untitled": "未列出",
"untop": {
"title": "取消置頂"
},
Expand Down
81 changes: 81 additions & 0 deletions client/src/components/adjacent_feed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import {useEffect, useState} from "react";
import {client} from "../main.tsx";
import {timeago} from "../utils/timeago.ts";
import {Link} from "wouter";
import {useTranslation} from "react-i18next";

export type AdjacentFeed = {
id: number;
title: string | null;
summary: string;
hashtags: {
id: number;
name: string;
}[];
createdAt: Date;
updatedAt: Date;
};
export type AdjacentFeeds = {
nextFeed: AdjacentFeed | null;
previousFeed: AdjacentFeed | null;
};

export function AdjacentSection({id, setError}: { id: string, setError: (error: string) => void }) {
const [adjacentFeeds, setAdjacentFeeds] = useState<AdjacentFeeds>();

useEffect(() => {
client.feed
.adjacent({id})
.get()
.then(({data, error}) => {
if (error) {
setError(error.value as string);
} else if (data && typeof data !== "string") {
setAdjacentFeeds(data);
}
});
}, [id, setError]);
return (
<div className="rounded-2xl bg-w m-2 grid grid-cols-1 sm:grid-cols-2">
<AdjacentCard data={adjacentFeeds?.previousFeed} type="previous"/>
<AdjacentCard data={adjacentFeeds?.nextFeed} type="next"/>
</div>
)
}

export function AdjacentCard({data, type}: { data: AdjacentFeed | null | undefined, type: "previous" | "next" }) {
const direction = type === "previous" ? "text-start" : "text-end"
const radius = type === "previous" ? "rounded-l-2xl" : "rounded-r-2xl"
const {t} = useTranslation()
if (!data) {
return (<div className="w-full p-6 duration-300">
<p className={`t-secondary w-full ${direction}`}>
{type === "previous" ? "Previous" : "Next"}
</p>
<h1 className={`text-xl text-gray-700 dark:text-white text-pretty truncate ${direction}`}>
{t('no_more')}
</h1>
</div>);
}
return (
<Link href={`/feed/${data.id}`} target="_blank"
className={`w-full p-6 duration-300 bg-button ${radius}`}>
<p className={`t-secondary w-full ${direction}`}>
{type === "previous" ? "Previous" : "Next"}
</p>
<h1 className={`text-xl font-bold text-gray-700 dark:text-white text-pretty truncate ${direction}`}>
{data.title}
</h1>
<p className={`space-x-2 ${direction}`}>
<span className="text-gray-400 text-sm" title={new Date(data.createdAt).toLocaleString()}>
{data.createdAt === data.updatedAt ? timeago(data.createdAt) : t('feed_card.published$time', {time: timeago(data.createdAt)})}
</span>
{data.createdAt !== data.updatedAt &&
<span className="text-gray-400 text-sm" title={new Date(data.updatedAt).toLocaleString()}>
{t('feed_card.updated$time', {time: timeago(data.updatedAt)})}
</span>
}
</p>
</Link>
)
}
17 changes: 9 additions & 8 deletions client/src/components/feed_card.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Link } from "wouter";
import { useTranslation } from "react-i18next";
import { timeago } from "../utils/timeago";
import { HashTag } from "./hashtag";
import { useMemo } from "react";
import {Link} from "wouter";
import {useTranslation} from "react-i18next";
import {timeago} from "../utils/timeago";
import {HashTag} from "./hashtag";
import {useMemo} from "react";

export function FeedCard({ id, title, avatar, draft, listed, top, summary, hashtags, createdAt, updatedAt }:
{
id: string, avatar?: string,
Expand Down Expand Up @@ -34,10 +35,10 @@ export function FeedCard({ id, title, avatar, draft, listed, top, summary, hasht
}
</p>
<p className="space-x-2">
{draft === 1 && <span className="text-gray-400 text-sm">草稿</span>}
{listed === 0 && <span className="text-gray-400 text-sm">未列出</span>}
{draft === 1 && <span className="text-gray-400 text-sm">{t("draft")}</span>}
{listed === 0 && <span className="text-gray-400 text-sm">{t("unlisted")}</span>}
{top === 1 && <span className="text-theme text-sm">
置顶
{t('article.top.title')}
</span>}
</p>
<p className="text-pretty overflow-hidden dark:text-neutral-500">
Expand Down
8 changes: 4 additions & 4 deletions client/src/page/callback.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useEffect } from "react";
import { setCookie } from "typescript-cookie";
import { useLocation, useSearch } from "wouter";
import {useEffect} from "react";
import {setCookie} from "typescript-cookie";
import {useLocation, useSearch} from "wouter";

export function CallbackPage() {
const searchParams = new URLSearchParams(useSearch());
const [_, setLocation] = useLocation();
const [, setLocation] = useLocation();
useEffect(() => {
const token = searchParams.get('token');
if (token) {
Expand Down
40 changes: 22 additions & 18 deletions client/src/page/feed.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import { useContext, useEffect, useRef, useState } from "react";
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import {useContext, useEffect, useRef, useState} from "react";
import {Helmet} from "react-helmet";
import {useTranslation} from "react-i18next";
import ReactModal from "react-modal";
import Popup from "reactjs-popup";
import { Link, useLocation } from "wouter";
import { useAlert, useConfirm } from "../components/dialog";
import { HashTag } from "../components/hashtag";
import { Waiting } from "../components/loading";
import { Markdown } from "../components/markdown";
import { client } from "../main";
import { ClientConfigContext } from "../state/config";
import { ProfileContext } from "../state/profile";
import { headersWithAuth } from "../utils/auth";
import { siteName } from "../utils/constants";
import { timeago } from "../utils/timeago";
import { Button } from "../components/button";
import { Tips } from "../components/tips";
import { useLoginModal } from "../hooks/useLoginModal";
import {Link, useLocation} from "wouter";
import {useAlert, useConfirm} from "../components/dialog";
import {HashTag} from "../components/hashtag";
import {Waiting} from "../components/loading";
import {Markdown} from "../components/markdown";
import {client} from "../main";
import {ClientConfigContext} from "../state/config";
import {ProfileContext} from "../state/profile";
import {headersWithAuth} from "../utils/auth";
import {siteName} from "../utils/constants";
import {timeago} from "../utils/timeago";
import {Button} from "../components/button";
import {Tips} from "../components/tips";
import {useLoginModal} from "../hooks/useLoginModal";
import mermaid from "mermaid";
import {AdjacentSection} from "../components/adjacent_feed.tsx";

type Feed = {
id: number;
Expand All @@ -39,14 +40,16 @@ type Feed = {
uv: number;
};



export function FeedPage({ id, TOC, clean }: { id: string, TOC: () => JSX.Element, clean: (id: string) => void }) {
const { t } = useTranslation();
const profile = useContext(ProfileContext);
const [feed, setFeed] = useState<Feed>();
const [error, setError] = useState<string>();
const [headImage, setHeadImage] = useState<string>();
const ref = useRef("");
const [_, setLocation] = useLocation();
const [, setLocation] = useLocation();
const { showAlert, AlertUI } = useAlert();
const { showConfirm, ConfirmUI } = useConfirm();
const [top, setTop] = useState<number>(0);
Expand Down Expand Up @@ -296,6 +299,7 @@ export function FeedPage({ id, TOC, clean }: { id: string, TOC: () => JSX.Elemen
</div>
</div>
</article>
<AdjacentSection id={id} setError={setError}/>
{feed && <Comments id={`${feed.id}`} />}
<div className="h-16" />
</main>
Expand Down
24 changes: 16 additions & 8 deletions client/src/page/settings.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import * as Switch from '@radix-ui/react-switch';
import { ChangeEvent, useContext, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import {ChangeEvent, useContext, useEffect, useRef, useState} from "react";
import {useTranslation} from "react-i18next";
import ReactLoading from "react-loading";
import Modal from "react-modal";
import { Button } from "../components/button.tsx";
import { useAlert, useConfirm } from "../components/dialog.tsx";
import { client, oauth_url } from "../main.tsx";
import { ClientConfigContext, ConfigWrapper, defaultClientConfig, defaultClientConfigWrapper, defaultServerConfig, defaultServerConfigWrapper, ServerConfigContext } from "../state/config.tsx";
import { headersWithAuth } from "../utils/auth.ts";
import {Button} from "../components/button.tsx";
import {useAlert, useConfirm} from "../components/dialog.tsx";
import {client, oauth_url} from "../main.tsx";
import {
ClientConfigContext,
ConfigWrapper,
defaultClientConfig,
defaultClientConfigWrapper,
defaultServerConfig,
defaultServerConfigWrapper,
ServerConfigContext
} from "../state/config.tsx";
import {headersWithAuth} from "../utils/auth.ts";
import '../utils/thumb.css';


Expand Down Expand Up @@ -81,7 +89,7 @@ export function Settings() {
<div className="flex flex-col justify-center items-center">
<ServerConfigContext.Provider value={serverConfig}>
<ClientConfigContext.Provider value={clientConfig}>
<main className="wauto rounded-2xl bg-w m-2 p-6" aria-label="正文">
<main className="wauto rounded-2xl bg-w m-2 p-6" aria-label={t("main_content")}>
<div className="flex flex-row items-center space-x-2">
<h1 className="text-2xl font-bold t-primary">
{t('settings.title')}
Expand Down
19 changes: 10 additions & 9 deletions client/src/page/timeline.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { useEffect, useRef, useState } from "react"
import { Helmet } from 'react-helmet'
import { Link } from "wouter"
import { Waiting } from "../components/loading"
import { client } from "../main"
import { headersWithAuth } from "../utils/auth"
import { siteName } from "../utils/constants"
import { useTranslation } from "react-i18next";
import {useEffect, useRef, useState} from "react"
import {Helmet} from 'react-helmet'
import {Link} from "wouter"
import {Waiting} from "../components/loading"
import {client} from "../main"
import {headersWithAuth} from "../utils/auth"
import {siteName} from "../utils/constants"
import {useTranslation} from "react-i18next";


export function TimelinePage() {
Expand Down Expand Up @@ -63,7 +63,8 @@ export function TimelinePage() {
</h1>
<div className="w-full flex flex-col justify-center items-start my-4">
{feeds[+year]?.map(({ id, title, createdAt }) => (
<FeedItem key={id} id={id.toString()} title={title || t('untitled')} createdAt={new Date(createdAt)} />
<FeedItem key={id} id={id.toString()} title={title || t('unlisted')}
createdAt={new Date(createdAt)}/>
))}
</div>
</div>
Expand Down
Loading

0 comments on commit 13b077d

Please sign in to comment.