Skip to content

Commit 6b41799

Browse files
committed
fix(problems): 添加随机一题按钮
老版答题页中有随机一题的按钮,如果选择随机刷题的话,可以刷完一题之后, 点按钮开始下一题,新版答题页并没有实现随机一题的按钮,目前先用扩展实现, 后面如果官方实现了随机一题之后,在去掉这个补丁。
1 parent a10ad9d commit 6b41799

3 files changed

Lines changed: 134 additions & 12 deletions

File tree

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import React, { FC, useEffect, useState } from 'react'
2+
import styled, { ThemeProvider } from 'styled-components/macro'
3+
4+
import { LeetCodeApi } from './leetcode-api'
5+
import { getTheme } from '../utils'
6+
import { darkTheme, lightTheme } from '../../theme'
7+
8+
const StyledBtn = styled.button`
9+
line-height: 0;
10+
color: ${props =>
11+
props.theme.mode === 'dark' ? 'rgb(219 219 219)' : 'rgb(38 38 38)'};
12+
& > svg {
13+
height: 18px;
14+
width: 18px;
15+
}
16+
`
17+
18+
const api = new LeetCodeApi(location.origin)
19+
20+
const Random: FC = () => {
21+
const [theme, setTheme] = useState(getTheme())
22+
useEffect(() => {
23+
// 跟随力扣的明暗主题进行切换
24+
const observer = new MutationObserver(mutationList => {
25+
if (mutationList.some(record => record.attributeName === 'class')) {
26+
if (document.documentElement.classList.contains('dark')) {
27+
setTheme(darkTheme)
28+
} else {
29+
setTheme(lightTheme)
30+
}
31+
}
32+
})
33+
34+
observer.observe(document.documentElement, { attributes: true })
35+
return () => {
36+
observer.disconnect()
37+
}
38+
}, [])
39+
40+
const handldClick = async () => {
41+
const allQuestions = await api.getAllQuestions()
42+
let i = Math.floor(Math.random() * (allQuestions.length - 1))
43+
if (allQuestions[i].titleSlug === location.pathname.split('/')[1]) {
44+
i = allQuestions.length - 1
45+
}
46+
;(window as any).next.router.push(`/problems/${allQuestions[i].titleSlug}/`)
47+
}
48+
return (
49+
<ThemeProvider theme={theme}>
50+
<StyledBtn onClick={handldClick}>
51+
<svg
52+
xmlns="http://www.w3.org/2000/svg"
53+
viewBox="0 0 24 24"
54+
width="1em"
55+
height="1em"
56+
fill="currentColor"
57+
>
58+
<path
59+
fillRule="evenodd"
60+
d="M18.48 17.5h-2.204a5 5 0 01-4.31-2.466l-.625-1.061-.624 1.061a5 5 0 01-4.31 2.466H2.661a1 1 0 110-2h3.746a3 3 0 002.586-1.48L10.181 12 8.993 9.98A3 3 0 006.407 8.5H2.661a1 1 0 110-2h3.746a5 5 0 014.31 2.466l.624 1.061.624-1.061a5 5 0 014.31-2.466h2.205V4.315a.5.5 0 01.874-.332l2.536 2.853a1 1 0 010 1.328l-2.536 2.853a.5.5 0 01-.874-.332V8.5h-2.204a3 3 0 00-2.587 1.48L12.501 12l1.188 2.02a3 3 0 002.587 1.48h2.204v-2.185a.5.5 0 01.874-.332l2.83 3.185a.5.5 0 010 .664l-2.83 3.185a.5.5 0 01-.874-.332V17.5z"
61+
clipRule="evenodd"
62+
/>
63+
</svg>
64+
</StyledBtn>
65+
</ThemeProvider>
66+
)
67+
}
68+
69+
export default Random

src/content/pages/problems/index.tsx

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ import { StrictMode } from 'react'
22
import ReactDOM, { render } from 'react-dom'
33

44
import Clock from './Clock'
5+
import Random from './Random'
56
import { getRoot, isBetaUI } from './utils'
7+
import { findElement } from '../../utils'
68

7-
let root: HTMLDivElement | null = null
8-
let titleSlug = ''
9+
let root: HTMLDivElement | null = null,
10+
randomRoot: HTMLDivElement | null = null,
11+
titleSlug = ''
912

1013
async function load() {
1114
titleSlug = location.pathname.split('/').filter(Boolean)[1]
@@ -14,7 +17,6 @@ async function load() {
1417
if (beta) {
1518
// 使用新版 UI
1619
if (parent && parent instanceof HTMLElement) {
17-
// parent.style.justifyContent = 'space-between'
1820
root = document.createElement('div')
1921
parent.prepend(root)
2022
root.style.display = 'flex'
@@ -47,6 +49,37 @@ async function load() {
4749
}
4850
}
4951

52+
/** 加载`随机一题`按钮
53+
*
54+
* 旧版答题页已经有`随机一题`的按钮,所以只需要在新版答题页中添加即可,
55+
* 新版答题页中的`上一题`和`下一题`按钮是放在上方导航栏处,
56+
* 所以`随机一题`按钮也跟它们一起放在导航栏处。
57+
*
58+
* 因为导航栏在进行题目切换的时候,并不会变化,
59+
* 所以不用像计时组件那样,每次在切换题目的时候都需要卸载后重新加载,
60+
* 只需要考虑跟其他非答题页之间进行切换的逻辑即可。
61+
*/
62+
async function loadRandom() {
63+
const beta = await isBetaUI()
64+
if (beta) {
65+
if (!randomRoot) {
66+
const nav = await findElement(
67+
'#__next > div > div > div > nav > div > div > div:nth-child(2)'
68+
)
69+
randomRoot = document.createElement('div')
70+
randomRoot.style.lineHeight = '0'
71+
nav.append(randomRoot)
72+
73+
render(
74+
<StrictMode>
75+
<Random />
76+
</StrictMode>,
77+
randomRoot
78+
)
79+
}
80+
}
81+
}
82+
5083
const isProblemPage = () => {
5184
const strs = location.pathname.split('/').filter(Boolean)
5285
return strs[0] === 'problems'
@@ -56,6 +89,7 @@ void (async function main() {
5689
if (isProblemPage()) {
5790
const beta = await isBetaUI()
5891
if (beta) {
92+
loadRandom()
5993
const params = location.pathname.split('/').filter(Boolean)
6094
// 新版 UI 中,如果一开始打开的就是题解页面,则当前并不存在提交栏,也就无法也不需要去挂载即使组件
6195
if (params[2] === 'solutions' && params[3]) return
@@ -64,10 +98,11 @@ void (async function main() {
6498
}
6599
})()
66100

67-
function unmount() {
68-
if (root) {
69-
ReactDOM.unmountComponentAtNode(root)
70-
root = null
101+
function unmount(el: HTMLElement | null) {
102+
if (el) {
103+
ReactDOM.unmountComponentAtNode(el)
104+
el.remove()
105+
el = null
71106
titleSlug = ''
72107
}
73108
}
@@ -89,21 +124,23 @@ if (isProblemPage() || location.pathname === '/problemset/all/') {
89124

90125
if (!isProblemPage()) {
91126
// 从答题页跳转到非答题页时,卸载计时组件
92-
unmount()
127+
unmount(root)
128+
unmount(randomRoot)
93129
} else {
94130
const beta = await isBetaUI()
95131
if (beta) {
132+
loadRandom()
96133
const params = location.pathname.split('/').filter(Boolean)
97134
// 新版 UI 中,跳转到题解页面之后,如果跳转回去,会导致提交栏发生变化,需要先卸载掉。
98135
if (params[2] === 'solutions' && params[3]) {
99-
unmount()
136+
unmount(root)
100137
return
101138
}
102139
}
103140
// 在答题页之间相互跳转,如果还是在同一题,则不做任何操作,如果是跳转另外一题则重新开始计时
104141
const curSlug = location.pathname.split('/').filter(Boolean)[1]
105142
if (curSlug !== titleSlug) {
106-
unmount()
143+
unmount(root)
107144
load()
108145
}
109146
}

src/content/pages/utils.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
// 添加监听 url 变化的事件
1+
import { DefaultTheme } from 'styled-components/macro'
2+
import { darkTheme, lightTheme } from '../theme'
3+
4+
/** 添加监听 url 变化的事件
5+
*
6+
*/
27
const initUrlChangeEvent = (() => {
38
let isLoad = false
49
const load = () => {
@@ -27,4 +32,15 @@ const initUrlChangeEvent = (() => {
2732
return load
2833
})()
2934

30-
export { initUrlChangeEvent }
35+
/** 获取当前力扣的主题
36+
*
37+
*/
38+
const getTheme = (): DefaultTheme => {
39+
const lcDarkDide = localStorage.getItem('lc-dark-side')
40+
if (lcDarkDide === 'dark') {
41+
return darkTheme
42+
}
43+
return lightTheme
44+
}
45+
46+
export { initUrlChangeEvent, getTheme }

0 commit comments

Comments
 (0)