Skip to content

Commit e62a4cc

Browse files
Add: 历史记录功能
1 parent 5e0df7e commit e62a4cc

File tree

14 files changed

+400
-360
lines changed

14 files changed

+400
-360
lines changed

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ jobs:
1919
- name: Enable Corepack
2020
run: corepack enable
2121

22-
- name: Set Node.js 22.2.0
22+
- name: Set Node.js
2323
uses: actions/setup-node@v3
2424
with:
25-
node-version: 22.2.0
25+
node-version-file: '.nvmrc'
2626
cache: 'yarn'
2727

2828
- name: Install dependencies

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"hast-util-to-jsx-runtime": "^2.3.0",
3030
"highlight.js": "^11.10.0",
3131
"katex": "^0.16.11",
32+
"lg-markdown-processor": "https://github.com/Mr-Python-in-China/lg-markdown-processor.git",
3233
"lodash": "^4.17.21",
3334
"mdast-util-math": "^3.0.0",
3435
"micromark-factory-space": "^2.0.0",
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import React from 'react';
2+
import {
3+
ArticleHistoryStorageItem,
4+
getHistoryStorage,
5+
setHistoryStorage
6+
} from './storage';
7+
import { Button, Card, Input, Link } from '@fluentui/react-components';
8+
import useRefreshHook from '../../../refreshHook';
9+
import { formatTime } from '../../../utils';
10+
import { Delete24Regular } from '@fluentui/react-icons';
11+
12+
function HistoryItem({
13+
item,
14+
onDelete
15+
}: {
16+
item: ArticleHistoryStorageItem;
17+
onDelete?: () => void;
18+
}) {
19+
return (
20+
<Card
21+
onClick={() => open('https://www.luogu.com.cn/article/' + item.lid)}
22+
floatingAction={
23+
<Button
24+
appearance="subtle"
25+
icon={
26+
<Button
27+
appearance="subtle"
28+
aria-label="Delete"
29+
icon={<Delete24Regular />}
30+
onClick={e => {
31+
e.stopPropagation();
32+
onDelete && onDelete();
33+
}}
34+
/>
35+
}
36+
/>
37+
}
38+
>
39+
<div className="articleHistoryItemCardName">{item.name}</div>
40+
<div>
41+
{item.status === true ? '通过' : '拒绝:' + item.status} @{' '}
42+
{formatTime(item.time)}
43+
</div>
44+
{item.forProblem && (
45+
<div>
46+
关联于题目{' '}
47+
<Link
48+
href={'https://www.luogu.com.cn/problem/' + item.forProblem}
49+
target="_blank"
50+
onClick={e => e.stopPropagation()}
51+
>
52+
{item.forProblem}
53+
</Link>
54+
</div>
55+
)}
56+
</Card>
57+
);
58+
}
59+
60+
export default function ArticleHistory() {
61+
const data = getHistoryStorage();
62+
const refresh = useRefreshHook();
63+
return (
64+
<>
65+
<div className="articleHistoryKpiDiv">
66+
KPI:
67+
<Input
68+
type="number"
69+
step={1}
70+
value={data.kpi.toString()}
71+
onChange={(_, d) => (
72+
setHistoryStorage({
73+
kpi: Math.floor(Number(d.value)),
74+
history: data.history
75+
}),
76+
refresh()
77+
)}
78+
/>
79+
</div>
80+
<div className="articleHistoryItemDiv">
81+
{data.history.reverse().map((item, index) => (
82+
<HistoryItem
83+
item={item}
84+
onDelete={() => {
85+
data.history.splice(index, 1);
86+
setHistoryStorage(data);
87+
refresh();
88+
}}
89+
key={index}
90+
/>
91+
))}
92+
</div>
93+
</>
94+
);
95+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
export interface ArticleHistoryStorageItem {
2+
time: number;
3+
lid: string;
4+
forProblem?: string;
5+
name: string;
6+
status: string | true;
7+
}
8+
9+
export interface ArticleHistoryStorage {
10+
kpi: number;
11+
history: ArticleHistoryStorageItem[];
12+
}
13+
14+
const StorageKey = 'articleHistory' as const;
15+
16+
export function getHistoryStorage() {
17+
return (
18+
(JSON.parse(
19+
localStorage.getItem(StorageKey) || 'null'
20+
) as ArticleHistoryStorage) ||
21+
({ kpi: 0, history: [] } satisfies ArticleHistoryStorage)
22+
);
23+
}
24+
25+
export function setHistoryStorage(data: ArticleHistoryStorage) {
26+
localStorage.setItem(StorageKey, JSON.stringify(data));
27+
}
28+
29+
export function appendHistoryStorage(...item: ArticleHistoryStorageItem[]) {
30+
const storage = getHistoryStorage();
31+
storage.history.push(...item);
32+
++storage.kpi;
33+
setHistoryStorage(storage);
34+
}
Lines changed: 36 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,8 @@
1-
/*
2-
* Copyright © 2024 by Luogu
3-
*/
4-
5-
import remarkGfm from 'remark-gfm';
6-
import remarkParse from 'remark-parse';
7-
import remarkMath from './remarkMath.js';
8-
import remarkRehype from 'remark-rehype';
9-
import { PluggableList, unified } from 'unified';
10-
import rehypeKatex from 'rehype-katex';
11-
import rehypeReact from 'rehype-react';
12-
import { visit } from 'unist-util-visit';
13-
import parsePath from 'parse-path';
141
import * as props from 'react/jsx-runtime';
2+
import rehypeReact from 'rehype-react';
3+
import rehypeHighlight from 'rehype-highlight';
4+
import { visit, SKIP } from 'unist-util-visit';
5+
import luoguMarkdownProcessor from 'lg-markdown-processor';
156

167
const rehypeReactConfig: import('hast-util-to-jsx-runtime').Options = {
178
Fragment: 'article',
@@ -21,83 +12,42 @@ const rehypeReactConfig: import('hast-util-to-jsx-runtime').Options = {
2112
jsxs: props.jsxs
2213
};
2314

24-
export default function getProcessor({
25-
remarkPlugins,
26-
rehypePlugins
27-
}: { remarkPlugins?: PluggableList; rehypePlugins?: PluggableList } = {}) {
28-
return unified()
29-
.use(remarkParse)
30-
.use(remarkGfm)
31-
.use(remarkMath)
32-
.use(remarkPlugins || [])
33-
.use(remarkRehype)
34-
.use(rehypeKatex)
35-
.use(hastBilibili)
36-
.use(rehypePlugins || [])
15+
export default function getProcessor() {
16+
return luoguMarkdownProcessor({
17+
rehypePlugins: [hastHeilightSpace, rehypeHighlight]
18+
})
3719
.use(rehypeReact, rehypeReactConfig)
3820
.freeze();
3921
}
40-
41-
function hastBilibili() {
22+
function hastHeilightSpace() {
23+
const heilightSpaceElement: import('hast').Element = {
24+
type: 'element',
25+
tagName: 'span',
26+
properties: { className: ['articleViewer-heilightSpace'] },
27+
children: [{ type: 'text', value: ' ' }]
28+
};
4229
return (tree: import('hast').Root) =>
43-
visit(tree, 'element', function (element) {
44-
if (element.tagName !== 'img' || !element.properties) return;
45-
const src = element.properties.src;
46-
if (typeof src !== 'string') return;
47-
if (!src.startsWith('bilibili:')) return;
48-
const parsedUrl = parsePath(src);
49-
if ((parsedUrl.protocol as string) !== 'bilibili') return;
50-
const query = parsedUrl.query;
51-
const r: {
52-
aid?: string;
53-
bvid?: string;
54-
page?: string;
55-
danmaku: string;
56-
autoplay: string;
57-
playlist: string;
58-
high_quality: string;
59-
} = {
60-
danmaku: '0',
61-
autoplay: '0',
62-
playlist: '0',
63-
high_quality: '1'
64-
};
65-
const pathname = parsedUrl.pathname;
66-
const match = pathname.match(/^(av)?(\d+)$/);
67-
if (match) r.aid = match[2];
68-
else if (pathname.toLowerCase().startsWith('bv')) r.bvid = pathname;
69-
else r.bvid = 'bv' + pathname;
70-
71-
const page = Number(query.t || '');
72-
if (page) r.page = String(page);
73-
74-
element.tagName = 'div';
75-
element.properties.style = 'position: relative; padding-bottom: 62.5%';
76-
77-
element.children = [
78-
{
79-
type: 'element',
80-
tagName: 'iframe',
81-
properties: {
82-
src:
83-
'https://www.bilibili.com/blackboard/webplayer/embed-old.html?' +
84-
Object.entries(r)
85-
.filter(([_, value]) => value !== undefined)
86-
.map(
87-
([key, value]) =>
88-
`${encodeURIComponent(String(key))}=${encodeURIComponent(String(value))}`
89-
)
90-
.join('&'),
91-
scrolling: 'no',
92-
border: 0,
93-
frameborder: 'no',
94-
framespacing: 0,
95-
allowfullscreen: true,
96-
style:
97-
'position: absolute; top: 0; left: 0; width: 100%; height: 100%;'
98-
},
99-
children: []
30+
visit(tree, 'element', element => {
31+
if (element === heilightSpaceElement) return SKIP;
32+
if (element.tagName === 'code') return SKIP;
33+
let className = element.properties['className'];
34+
if (typeof className === 'string') className = [className];
35+
if (
36+
Array.isArray(className) &&
37+
(className.includes('katex') || className.includes('katex-display'))
38+
)
39+
return SKIP;
40+
const newChildren = new Array<import('hast').ElementContent>();
41+
for (let i of element.children) {
42+
if (i.type !== 'text') {
43+
newChildren.push(i);
44+
continue;
10045
}
101-
];
46+
i.value.split(' ').forEach((s, i) => {
47+
if (i) newChildren.push(heilightSpaceElement);
48+
newChildren.push({ type: 'text', value: s });
49+
});
50+
}
51+
element.children = newChildren;
10252
});
10353
}

src/app/features/article/articleViewer/index.ts

Lines changed: 1 addition & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,42 +8,7 @@ import 'highlight.js/styles/base16/tomorrow.css';
88
import './style.css';
99
import './highlightFormatIssue.css';
1010

11-
function hastHeilightSpace() {
12-
const heilightSpaceElement: import('hast').Element = {
13-
type: 'element',
14-
tagName: 'span',
15-
properties: { className: ['articleViewer-heilightSpace'] },
16-
children: [{ type: 'text', value: ' ' }]
17-
};
18-
return (tree: import('hast').Root) =>
19-
visit(tree, 'element', element => {
20-
if (element === heilightSpaceElement) return SKIP;
21-
if (element.tagName === 'code') return SKIP;
22-
let className = element.properties['className'];
23-
if (typeof className === 'string') className = [className];
24-
if (
25-
Array.isArray(className) &&
26-
(className.includes('katex') || className.includes('katex-display'))
27-
)
28-
return SKIP;
29-
const newChildren = new Array<import('hast').ElementContent>();
30-
for (let i of element.children) {
31-
if (i.type !== 'text') {
32-
newChildren.push(i);
33-
continue;
34-
}
35-
i.value.split(' ').forEach((s, i) => {
36-
if (i) newChildren.push(heilightSpaceElement);
37-
newChildren.push({ type: 'text', value: s });
38-
});
39-
}
40-
element.children = newChildren;
41-
});
42-
}
43-
44-
const processor = getProcessor({
45-
rehypePlugins: [hastHeilightSpace, rehypeHighlight]
46-
});
11+
const processor = getProcessor();
4712

4813
const ArticleViewer = memo(function ({
4914
children: markdown

src/app/features/article/articleViewer/remarkMath.d.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)