mirror of
https://github.com/wgh136/nysoure.git
synced 2025-09-27 12:17:24 +00:00
Add AboutPage component and integrate sorting options in resource retrieval
This commit is contained in:
@@ -10,6 +10,7 @@ import ManagePage from "./pages/manage_page.tsx";
|
||||
import TaggedResourcesPage from "./pages/tagged_resources_page.tsx";
|
||||
import UserPage from "./pages/user_page.tsx";
|
||||
import EditResourcePage from "./pages/edit_resource_page.tsx";
|
||||
import AboutPage from "./pages/about_page.tsx";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
@@ -26,6 +27,7 @@ export default function App() {
|
||||
<Route path={"/tag/:tag"} element={<TaggedResourcesPage/>}/>
|
||||
<Route path={"/user/:username"} element={<UserPage/>}/>
|
||||
<Route path={"/resource/edit/:rid"} element={<EditResourcePage/>}/>
|
||||
<Route path={"/about"} element={<AboutPage/>}/>
|
||||
</Route>
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
|
@@ -319,6 +319,15 @@ export const i18nData = {
|
||||
"Set the description of the tag.": "设置标签的描述。",
|
||||
"Use markdown format.": "使用Markdown格式。",
|
||||
"Tag: ": "标签: ",
|
||||
|
||||
// 添加排序选项翻译
|
||||
"Select a Order": "选择排序方式",
|
||||
"Latest": "最新",
|
||||
"Oldest": "最早",
|
||||
"Most Viewed": "浏览最多",
|
||||
"Least Viewed": "浏览最少",
|
||||
"Most Downloaded": "下载最多",
|
||||
"Least Downloaded": "下载最少",
|
||||
}
|
||||
},
|
||||
"zh-TW": {
|
||||
@@ -480,6 +489,15 @@ export const i18nData = {
|
||||
"Set the description of the tag.": "設置標籤的描述。",
|
||||
"Use markdown format.": "使用Markdown格式。",
|
||||
"Tag: ": "標籤: ",
|
||||
|
||||
// 添加排序选项翻译
|
||||
"Select a Order": "選擇排序方式",
|
||||
"Latest": "最新",
|
||||
"Oldest": "最早",
|
||||
"Most Viewed": "瀏覽最多",
|
||||
"Least Viewed": "瀏覽最少",
|
||||
"Most Downloaded": "下載最多",
|
||||
"Least Downloaded": "下載最少",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -84,6 +84,9 @@ article {
|
||||
border-radius: 4px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, system-ui;
|
||||
}
|
||||
.line-break {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
|
||||
a.no-underline {
|
||||
|
@@ -126,3 +126,12 @@ export interface ServerConfig {
|
||||
server_description: string;
|
||||
site_info: string;
|
||||
}
|
||||
|
||||
export enum RSort {
|
||||
TimeAsc = 0,
|
||||
TimeDesc = 1,
|
||||
ViewsAsc = 2,
|
||||
ViewsDesc = 3,
|
||||
DownloadsAsc = 4,
|
||||
DownloadsDesc = 5,
|
||||
}
|
||||
|
@@ -14,7 +14,7 @@ import {
|
||||
UserWithToken,
|
||||
Comment,
|
||||
CommentWithResource,
|
||||
ServerConfig
|
||||
ServerConfig, RSort
|
||||
} from "./models.ts";
|
||||
|
||||
class Network {
|
||||
@@ -409,11 +409,12 @@ class Network {
|
||||
}
|
||||
}
|
||||
|
||||
async getResources(page: number): Promise<PageResponse<Resource>> {
|
||||
async getResources(page: number, sort: RSort): Promise<PageResponse<Resource>> {
|
||||
try {
|
||||
const response = await axios.get(`${this.apiBaseUrl}/resource`, {
|
||||
params: {
|
||||
page
|
||||
page,
|
||||
sort,
|
||||
}
|
||||
})
|
||||
return response.data
|
||||
|
10
frontend/src/pages/about_page.tsx
Normal file
10
frontend/src/pages/about_page.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import Markdown from "react-markdown";
|
||||
import {app} from "../app.ts";
|
||||
|
||||
export default function AboutPage() {
|
||||
return <article className={"p-4"}>
|
||||
<Markdown>
|
||||
{app.siteInfo}
|
||||
</Markdown>
|
||||
</article>
|
||||
}
|
@@ -2,36 +2,63 @@ import {useEffect, useState} from "react";
|
||||
import ResourcesView from "../components/resources_view.tsx";
|
||||
import {network} from "../network/network.ts";
|
||||
import { app } from "../app.ts";
|
||||
import Markdown from "react-markdown";
|
||||
import {RSort} from "../network/models.ts";
|
||||
import Button from "../components/button.tsx";
|
||||
import {MdInfoOutline} from "react-icons/md";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {useNavigate} from "react-router";
|
||||
|
||||
export default function HomePage() {
|
||||
useEffect(() => {
|
||||
document.title = app.appName;
|
||||
}, [])
|
||||
|
||||
const [isCollapsed, setIsCollapsed] = useState(false);
|
||||
const [order, setOrder] = useState(RSort.TimeAsc)
|
||||
|
||||
const {t} = useTranslation()
|
||||
|
||||
const navigate = useNavigate()
|
||||
|
||||
return <>
|
||||
{
|
||||
app.siteInfo && <div className={"mt-4 px-4"}>
|
||||
<div className="collapse collapse-arrow bg-base-100 border border-base-300" onClick={() => setIsCollapsed(!isCollapsed)}>
|
||||
<input type="radio" name="my-accordion-2" checked={isCollapsed} style={{
|
||||
"cursor": "pointer",
|
||||
}}/>
|
||||
<div className="collapse-title font-semibold cursor-pointer">{t("About this site")}</div>
|
||||
<article className="collapse-content text-sm cursor-auto" onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}>
|
||||
<Markdown>
|
||||
{app.siteInfo}
|
||||
</Markdown>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
<div className={"flex p-4 items-center"}>
|
||||
<select value={order} className="select w-52 select-info" onInput={(e) => {
|
||||
const value = e.currentTarget.value;
|
||||
if (value === "0") {
|
||||
setOrder(RSort.TimeAsc);
|
||||
} else if (value === "1") {
|
||||
setOrder(RSort.TimeDesc);
|
||||
} else if (value === "2") {
|
||||
setOrder(RSort.ViewsDesc);
|
||||
} else if (value === "3") {
|
||||
setOrder(RSort.ViewsAsc);
|
||||
} else if (value === "4") {
|
||||
setOrder(RSort.DownloadsDesc);
|
||||
} else if (value === "5") {
|
||||
setOrder(RSort.DownloadsAsc);
|
||||
}
|
||||
<ResourcesView storageKey={"home_page"} loader={(page) => network.getResources(page)}></ResourcesView>
|
||||
}}>
|
||||
<option disabled>{t("Select a Order")}</option>
|
||||
<option value={0}>{t("Latest")}</option>
|
||||
<option value={1}>{t("Oldest")}</option>
|
||||
<option value={2}>{t("Most Viewed")}</option>
|
||||
<option value={3}>{t("Least Viewed")}</option>
|
||||
<option value={4}>{t("Most Downloaded")}</option>
|
||||
<option value={5}>{t("Least Downloaded")}</option>
|
||||
</select>
|
||||
<span className={"flex-1"}/>
|
||||
<Button onClick={() => {
|
||||
navigate("/about");
|
||||
}}>
|
||||
<div className={"flex items-center"}>
|
||||
<MdInfoOutline size={24} className={"inline-block mr-2"}/>
|
||||
<span>{t("About this site")}</span>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
<ResourcesView
|
||||
key={`home_page_${order}`}
|
||||
storageKey={`home_page_${order}`}
|
||||
loader={(page) => network.getResources(page, order)}
|
||||
/>
|
||||
</>
|
||||
}
|
@@ -101,7 +101,19 @@ func handleListResources(c fiber.Ctx) error {
|
||||
if err != nil {
|
||||
return model.NewRequestError("Invalid page number")
|
||||
}
|
||||
resources, maxPage, err := service.GetResourceList(page)
|
||||
sortStr := c.Query("sort")
|
||||
if sortStr == "" {
|
||||
sortStr = "0"
|
||||
}
|
||||
sortInt, err := strconv.Atoi(sortStr)
|
||||
if err != nil {
|
||||
return model.NewRequestError("Invalid sort parameter")
|
||||
}
|
||||
if sortInt < 0 || sortInt > 5 {
|
||||
return model.NewRequestError("Sort parameter out of range")
|
||||
}
|
||||
sort := model.RSort(sortInt)
|
||||
resources, maxPage, err := service.GetResourceList(page, sort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@@ -37,7 +37,7 @@ func GetResourceByID(id uint) (model.Resource, error) {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func GetResourceList(page, pageSize int) ([]model.Resource, int, error) {
|
||||
func GetResourceList(page, pageSize int, sort model.RSort) ([]model.Resource, int, error) {
|
||||
// Retrieve a list of resources with pagination
|
||||
var resources []model.Resource
|
||||
var total int64
|
||||
@@ -46,7 +46,25 @@ func GetResourceList(page, pageSize int) ([]model.Resource, int, error) {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if err := db.Offset((page - 1) * pageSize).Limit(pageSize).Preload("User").Preload("Images").Preload("Tags").Order("created_at DESC").Find(&resources).Error; err != nil {
|
||||
order := ""
|
||||
switch sort {
|
||||
case model.RSortTimeAsc:
|
||||
order = "created_at ASC"
|
||||
case model.RSortTimeDesc:
|
||||
order = "created_at DESC"
|
||||
case model.RSortViewsAsc:
|
||||
order = "views ASC"
|
||||
case model.RSortViewsDesc:
|
||||
order = "views DESC"
|
||||
case model.RSortDownloadsAsc:
|
||||
order = "downloads ASC"
|
||||
case model.RSortDownloadsDesc:
|
||||
order = "downloads DESC"
|
||||
default:
|
||||
order = "created_at DESC" // Default sort order
|
||||
}
|
||||
|
||||
if err := db.Offset((page - 1) * pageSize).Limit(pageSize).Preload("User").Preload("Images").Preload("Tags").Order(order).Find(&resources).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
|
12
server/model/sort.go
Normal file
12
server/model/sort.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package model
|
||||
|
||||
type RSort uint8
|
||||
|
||||
const (
|
||||
RSortTimeAsc RSort = iota
|
||||
RSortTimeDesc
|
||||
RSortViewsAsc
|
||||
RSortViewsDesc
|
||||
RSortDownloadsAsc
|
||||
RSortDownloadsDesc
|
||||
)
|
@@ -125,8 +125,8 @@ func GetResource(id uint, host string) (*model.ResourceDetailView, error) {
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
func GetResourceList(page int) ([]model.ResourceView, int, error) {
|
||||
resources, totalPages, err := dao.GetResourceList(page, pageSize)
|
||||
func GetResourceList(page int, sort model.RSort) ([]model.ResourceView, int, error) {
|
||||
resources, totalPages, err := dao.GetResourceList(page, pageSize, sort)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
Reference in New Issue
Block a user