Skip to content

Commit 50f39fb

Browse files
author
Iori Ichinose
committed
feat: add NewsPage
1 parent 7d74ec4 commit 50f39fb

12 files changed

Lines changed: 170 additions & 67 deletions

File tree

src/components/SideBar/SideBar.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,6 @@ const onInput = useDebounce(setFilter);
3232
<NSpace vertical style="padding: 0 12px">
3333
<NInput :placeholder="t('sider.searchText')" @input="onInput" />
3434
</NSpace>
35-
<NMenu :options="options" :value="route.path" default-expand-all />
35+
<NMenu :options="options" :value="route.path" />
3636
</NSpace>
3737
</template>

src/components/SideBar/SidePanel.vue

Lines changed: 15 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,35 @@
11
<script setup lang="ts">
2+
import { computed } from 'vue';
3+
import { useRouter } from 'vue-router';
24
import { useI18n } from 'vue-i18n';
3-
import {
4-
NCard,
5-
NTabs,
6-
NTabPane,
7-
NDivider,
8-
NRow,
9-
NButton,
10-
NModal
11-
} from 'naive-ui';
12-
13-
import {
14-
fetchNewsList,
15-
fetchJokes,
16-
JokeEntry,
17-
NewsEntry,
18-
fetchNews
19-
} from '@/models/news';
20-
import { convertTimestamp } from '@/utils/time';
5+
import { NCard, NTabs, NTabPane, NDivider, NRow, NButton } from 'naive-ui';
216
7+
import { useStore } from '@/store';
228
import { useMutableRef, usePromiseEffect } from '@/hooks';
9+
import { fetchJokes, JokeEntry } from '@/models/news';
10+
import { convertTimestamp } from '@/utils/time';
2311
24-
import MarkdownContainer from '@/components/MarkdownContainer';
25-
26-
interface NewsEntryWithContent extends NewsEntry {
27-
content: string;
28-
}
29-
12+
const store = useStore();
3013
const { t, locale } = useI18n();
31-
const [news, setNews] = useMutableRef([] as NewsEntry[]);
3214
const [jokes, setJokes] = useMutableRef([] as JokeEntry[]);
33-
const [selected, setSelected] = useMutableRef<NewsEntryWithContent | null>(
34-
null
35-
);
36-
const [show, setShow] = useMutableRef(false);
15+
const router = useRouter();
3716
38-
usePromiseEffect(fetchNewsList, setNews);
39-
usePromiseEffect(fetchJokes, setJokes);
17+
const panelLimit = 6;
18+
const entries = computed(() => store.state.newsEntries.slice(0, panelLimit));
19+
usePromiseEffect(() => fetchJokes(panelLimit), setJokes);
4020
41-
async function handleClick(link: NewsEntry) {
42-
setSelected({ ...link, content: await fetchNews(link) });
43-
setShow(true);
44-
}
21+
const handleClick = (index: number) => router.push(`/news/${index}`);
4522
</script>
4623

4724
<template>
4825
<NDivider title-placement="left">{{ t('sider.news') }}</NDivider>
4926
<NCard content-style="padding: 0 20px;" :bordered="false">
5027
<NTabs type="segment" size="small" pane-class="tab-pane">
5128
<NTabPane name="Mirror">
52-
<NRow v-for="link in news" :key="link.content">
53-
<NButton text tag="a" @click="handleClick(link)">
29+
<NRow v-for="(entry, index) in entries" :key="entry.content">
30+
<NButton text tag="a" @click="handleClick(index)">
5431
<span class="link-title">
55-
{{
56-
convertTimestamp(link.time, locale as 'zh' | 'en') +
57-
' - ' +
58-
link.name
59-
}}
32+
{{ convertTimestamp(entry.time, locale) + ' - ' + entry.name }}
6033
</span>
6134
</NButton>
6235
</NRow>
@@ -72,17 +45,6 @@ async function handleClick(link: NewsEntry) {
7245
</NTabPane>
7346
</NTabs>
7447
</NCard>
75-
<NModal
76-
style="width: min(600px, 90%)"
77-
preset="card"
78-
size="huge"
79-
:title="selected?.name"
80-
v-if="selected"
81-
v-model:show="show"
82-
bordered
83-
>
84-
<MarkdownContainer :content="selected.content" />
85-
</NModal>
8648
</template>
8749

8850
<style scoped lang="less">

src/components/TopNavi/TopNavi.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ const options = [
5353
createRoute('/', 'mirrors'),
5454
createRoute('/download', 'downloads'),
5555
createRoute('/help', 'help'),
56+
createRoute('/news', 'news'),
5657
createRoute('/about', 'about'),
5758
localeButton,
5859
darkMode

src/i18n/en.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"mirrors": "Mirrors",
66
"downloads": "Downloads",
77
"help": "Help",
8+
"news": "News",
89
"doc": "Documentation",
910
"about": "About",
1011
"error": "Error"
@@ -28,6 +29,12 @@
2829
"title": "RTFM",
2930
"description": "Select menu to view help"
3031
},
32+
"news": {
33+
"date": "Date",
34+
"author": "Author",
35+
"title": "News",
36+
"description": "Select menu to view news"
37+
},
3138
"footer": {
3239
"maintainer": "This mirror site is maintained by {name}",
3340
"developer": "Frontend by {name}",

src/i18n/zh.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"mirrors": "镜像列表",
66
"downloads": "下载",
77
"help": "帮助",
8+
"news": "新闻",
89
"doc": "帮助文档",
910
"about": "关于",
1011
"error": "出错了"
@@ -28,6 +29,12 @@
2829
"title": "去读手册",
2930
"description": "浏览菜单即可打开帮助"
3031
},
32+
"news": {
33+
"date": "发布日期",
34+
"author": "作者",
35+
"title": "镜像站新闻",
36+
"description": "浏览菜单即可查看新闻"
37+
},
3138
"footer": {
3239
"maintainer": "本镜像站由{name}维护",
3340
"developer": "前端由{name}构建",

src/models/news.ts

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,41 @@ export interface NewsEntry {
66
name: string;
77
time: number;
88
content: string;
9+
author?: string;
10+
tags?: string[];
911
}
1012

1113
export interface JokeEntry {
1214
title: string;
1315
web_url: string;
1416
}
1517

16-
export async function fetchJokes() {
17-
const links: JokeEntry[] = await json(
18+
export async function fetchJokes(limit?: number) {
19+
let links: JokeEntry[] = await json(
1820
'https://git.nju.edu.cn/api/v4/projects/2412/issues'
1921
);
20-
return links
21-
.map(link => ({
22-
...link,
23-
title: link.title.replace(/\d+$/g, '')
24-
}))
25-
.slice(0, 6);
22+
if (limit != undefined) {
23+
links = links.slice(0, limit);
24+
}
25+
return links.map(link => ({
26+
...link,
27+
title: link.title.replace(/\d+$/g, '')
28+
}));
2629
}
2730

28-
export async function fetchNewsList(): Promise<NewsEntry[]> {
29-
return await json(ServerPrefix + 'news/index.json');
31+
export async function fetchNewsList(limit?: number): Promise<NewsEntry[]> {
32+
let data: NewsEntry[] = await json(ServerPrefix + 'news/index.json');
33+
data.sort((a, b) => b.time - a.time);
34+
if (limit != undefined) {
35+
data = data.slice(0, limit);
36+
}
37+
return data;
3038
}
3139

32-
export async function fetchNews(item: NewsEntry): Promise<string> {
40+
export async function fetchNews(item?: NewsEntry): Promise<string> {
41+
if (item == undefined) {
42+
return Promise.resolve('No document available.');
43+
}
3344
return await text(ServerPrefix + `news/${item.content}`, data =>
3445
marked(data)
3546
);

src/routes/index.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { LoadingBarApi, MenuOption } from 'naive-ui';
33
import fetchDocRoutes from '@/routes/docs';
44
import fetchDownloadRoutes from '@/routes/downloads';
55
import { State } from '@/store';
6+
import fetchNewsRoutes from './news';
67

78
export const loadingRef: { value?: LoadingBarApi } = {};
89
export const setLoadingRef = (loadingBar: LoadingBarApi) => {
@@ -48,6 +49,21 @@ const routes: RouteRecordRaw[] = [
4849
sider: fetchDownloadRoutes
4950
}
5051
},
52+
{
53+
name: 'News',
54+
path: '/news',
55+
component: () => import('@/views/NewsPage.vue'),
56+
children: [
57+
{
58+
path: ':title',
59+
component: () => import('@/views/NewsPage.vue')
60+
}
61+
],
62+
meta: {
63+
title: 'News',
64+
sider: fetchNewsRoutes
65+
}
66+
},
5167
{
5268
name: 'About',
5369
path: '/about',

src/routes/news.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { State } from '@/store';
2+
import { convertTimestamp } from '@/utils/time';
3+
import { MenuOption } from 'naive-ui';
4+
import { RouterLink } from 'vue-router';
5+
6+
export default function fetchNewsRoutes(state: State, filter = '') {
7+
const entries = state.newsEntries.filter(e =>
8+
e.name.toLocaleLowerCase().includes(filter.toLocaleLowerCase())
9+
);
10+
return entries.map(
11+
(item, index) =>
12+
({
13+
key: '/news/' + index,
14+
label: () => {
15+
return (
16+
<RouterLink
17+
to={'/news/' + index}
18+
style="display: flex; justify-content: space-between; align-items: center"
19+
>
20+
<span>{item.name}</span>
21+
<span style="font-size: 12px; line-height: 1.0">
22+
{convertTimestamp(item.time, 'zh')}
23+
</span>
24+
</RouterLink>
25+
);
26+
}
27+
} as MenuOption)
28+
);
29+
}

src/store/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import { createStore, useStore as baseUseStore, Store } from 'vuex';
33
import { MobileWidth } from '@/configs';
44
import { DocItem } from '@/models/documents';
55
import { DownloadContent } from '@/models/downloads';
6+
import { NewsEntry } from '@/models/news';
67

78
export interface State {
89
isMobile: boolean;
910
darkMode: boolean;
1011
locale: 'zh' | 'en';
12+
newsEntries: NewsEntry[];
1113
docItems: DocItem[];
1214
downloadContents: DownloadContent[];
1315
}
@@ -19,6 +21,7 @@ export const store = createStore<State>({
1921
isMobile: document.body.clientWidth < MobileWidth,
2022
darkMode: sessionStorage.getItem('darkMode') == 'true',
2123
locale: 'zh',
24+
newsEntries: [],
2225
docItems: [],
2326
downloadContents: []
2427
},
@@ -30,6 +33,9 @@ export const store = createStore<State>({
3033
state.darkMode = darkMode;
3134
sessionStorage.setItem('darkMode', darkMode.toString());
3235
},
36+
setNews(state, entries: NewsEntry[]) {
37+
state.newsEntries = entries;
38+
},
3339
setDocs(state, items: DocItem[]) {
3440
state.docItems = items;
3541
},

src/utils/time.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ dayjs.extend(relativeTime);
77

88
export function convertTimestamp(
99
ts: number,
10-
locale: 'zh' | 'en',
10+
locale: string,
1111
format = 'YYYY.M.D'
1212
) {
1313
return dayjs.unix(ts).locale(locale).format(format);
1414
}
1515

16-
export function timeFromNow(ts: number, locale: 'zh' | 'en') {
16+
export function timeFromNow(ts: number, locale: string) {
1717
return ts <= 0 ? '-' : dayjs.unix(ts).locale(locale).fromNow();
1818
}

0 commit comments

Comments
 (0)