1- import { createMemo , createSignal , For , JSXElement , Show } from "solid-js" ;
1+ import {
2+ createMemo ,
3+ createResource ,
4+ createSignal ,
5+ For ,
6+ JSXElement ,
7+ Show ,
8+ } from "solid-js" ;
29
10+ import { cachedFetchJson } from "../../utils/json-data" ;
311import { AnimatedModal } from "../common/AnimatedModal" ;
412
13+ type FaqContentBlock =
14+ | { type : "paragraph" ; text : string }
15+ | { type : "list" ; items : string [ ] } ;
16+
517type FaqTopic = {
618 title : string ;
7- content : JSXElement ;
819 searchText : string ;
20+ content : FaqContentBlock [ ] ;
921} ;
1022
11- const faqTopics : FaqTopic [ ] = [
12- {
13- title : "How do I type coding symbols?" ,
14- searchText :
15- "coding symbols punctuation quotes custom text code special characters" ,
16- content : (
17- < div class = "flex flex-col gap-4" >
18- < p > There are two ways to type coding symbols in Monkeytype:</ p >
19- < ul class = "flex list-disc flex-col gap-2 pl-4" >
20- < li >
21- < span class = "text-text" > Punctuation mode</ span > - Enable it by
22- clicking the < span class = "text-text" > @ punctuation</ span > button in
23- the test config bar. This adds common symbols like{ " " }
24- < span class = "text-text" > ! , . ; : ' " ( )</ span >
25- </ li >
26- < li >
27- < span class = "text-text" > Quote mode</ span > - Switch to{ " " }
28- < span class = "text-text" > quote</ span > mode in the test config bar.
29- Quotes often contain real code-like punctuation and symbols.
30- </ li >
31- < li >
32- < span class = "text-text" > Custom text</ span > - Use the command line{ " " }
33- < span class = "text-text" > (Ctrl + Shift + P)</ span > and select
34- "custom text" to type any text you want, including code.
35- </ li >
36- </ ul >
37- </ div >
38- ) ,
39- } ,
40- {
41- title : "How do I restart the test quickly?" ,
42- searchText : "restart test quickly tab enter esc shortcut keyboard" ,
43- content : (
44- < div class = "flex flex-col gap-4" >
45- < p > You can restart the test without touching the mouse:</ p >
46- < ul class = "flex list-disc flex-col gap-2 pl-4" >
47- < li >
48- Press < span class = "text-text" > Tab + Enter</ span > - the default
49- shortcut to restart.
50- </ li >
51- < li >
52- Enable < span class = "text-text" > Quick Restart</ span > mode in settings
53- - restart with < span class = "text-text" > Tab or Esc or Enter</ span > .
54- </ li >
55- </ ul >
56- </ div >
57- ) ,
58- } ,
59- {
60- title : "How do I change the language?" ,
61- searchText : "language change switch foreign english spanish french german" ,
62- content : (
63- < div class = "flex flex-col gap-4" >
64- < p >
65- Search < span class = "text-text" > language</ span > in command line. A list
66- of all available languages will appear - select the one you want.
67- </ p >
68- < p > Monkeytype supports multiple languages.</ p >
69- </ div >
70- ) ,
71- } ,
72- {
73- title : "How do I change the theme?" ,
74- searchText : "theme color appearance dark light custom random palette" ,
75- content : (
76- < div class = "flex flex-col gap-4" >
77- < p > There are several ways to change the theme:</ p >
78- < ul class = "flex list-disc flex-col gap-2 pl-4" >
79- < li >
80- Open the < span class = "text-text" > command line</ span > with{ " " }
81- < span class = "text-text" > Ctrl + Shift + P</ span > and search for a
82- theme name.
83- </ li >
84- < li >
85- Go to < span class = "text-text" > Settings → Theme</ span > and browse the
86- full list.
87- </ li >
88- < li >
89- Enable < span class = "text-text" > random theme</ span > in settings to
90- get a new theme on every test. After completing a test, the theme
91- will be set to a random one. The random themes are not saved to your
92- config. If set to 'favorite' only favorite themes will be
93- randomized. If set to 'light' or 'dark', only
94- presets with light or dark background colors will be randomized. If
95- set to 'auto', dark or light themes are used depending on
96- your system theme. If set to 'custom', custom themes will
97- be randomized.
98- </ li >
99- < li >
100- Click the < span class = "text-text" > palette icon</ span > in the footer
101- to quickly switch themes.
102- </ li >
103- </ ul >
104- </ div >
105- ) ,
106- } ,
107- {
108- title : "How do I save my progress?" ,
109- searchText : "save progress account history personal best login signup" ,
110- content : (
111- < div class = "flex flex-col gap-4" >
112- < p >
113- To save your typing history and personal bests, create a free account.
114- </ p >
115- < p >
116- Click the < span class = "text-text" > person icon</ span > in the top right
117- to sign up or log in. Once logged in, all your results are
118- automatically saved.
119- </ p >
120- < p >
121- Without an account, results are only stored temporarily in your
122- browser session.
123- </ p >
124- </ div >
125- ) ,
126- } ,
127- {
128- title : "How do the leaderboards work?" ,
129- searchText :
130- "leaderboard rank top fastest qualify anticheat english 15 60 seconds" ,
131- content : (
132- < div class = "flex flex-col gap-4" >
133- < p >
134- The global leaderboards track the fastest typists for{ " " }
135- < span class = "text-text" > 15 second</ span > and{ " " }
136- < span class = "text-text" > 60 second</ span > English tests.
137- </ p >
138- < p > To qualify, your result must:</ p >
139- < ul class = "flex list-disc flex-col gap-2 pl-4" >
140- < li > Be completed while logged in</ li >
141- < li >
142- Use the < span class = "text-text" > English</ span > language
143- </ li >
144- < li > Have no funbox modifiers active</ li >
145- < li > Pass the anticheat verification</ li >
146- </ ul >
147- </ div >
148- ) ,
149- } ,
150- {
151- title : "What is Blind Mode?" ,
152- searchText : "blind mode errors hide mistakes accuracy training" ,
153- content : (
154- < div class = "flex flex-col gap-4" >
155- < p >
156- Blind mode hides your errors while typing - you won't see red
157- characters or highlights during the test.
158- </ p >
159- < p >
160- This is useful for training yourself to keep typing without fixating
161- on mistakes. Accuracy is still tracked and shown at the end.
162- </ p >
163- < p >
164- Enable it in{ " " }
165- < span class = "text-text" > Settings → Behavior → Blind Mode</ span > .
166- </ p >
167- </ div >
168- ) ,
169- } ,
170- {
171- title : "What is Pace Caret?" ,
172- searchText : "pace caret target speed race wpm goal second caret" ,
173- content : (
174- < div class = "flex flex-col gap-4" >
175- < p >
176- The pace caret is a second caret that moves at a target speed so you
177- can race against it.
178- </ p >
179- < p >
180- Set it to your personal best, average, or a custom WPM target in{ " " }
181- < span class = "text-text" > Settings → Caret → Pace Caret</ span > .
182- </ p >
183- </ div >
184- ) ,
185- } ,
186- {
187- title : "What are Funbox modes?" ,
188- searchText : "funbox fun modes gibberish numbers challenge special modifier" ,
189- content : (
190- < div class = "flex flex-col gap-4" >
191- < p >
192- Funbox modes are special modifiers that change how the test works.
193- Examples:
194- </ p >
195- < ul class = "flex list-disc flex-col gap-2 pl-4" >
196- < li >
197- < span class = "text-text" > gibberish</ span > - random nonsense words
198- </ li >
199- < li >
200- < span class = "text-text" > 58008</ span > - only numbers
201- </ li >
202- < li >
203- < span class = "text-text" > read ahead</ span > - hides the current word
204- </ li >
205- < li >
206- < span class = "text-text" > no quit</ span > - forces you to finish
207- </ li >
208- </ ul >
209- < p >
210- Access via < span class = "text-text" > Ctrl + Shift + P</ span > → funbox.
211- </ p >
212- </ div >
213- ) ,
214- } ,
215- {
216- title : "How do I use Custom Text?" ,
217- searchText : "custom text paste own code lyrics repeat randomize" ,
218- content : (
219- < div class = "flex flex-col gap-4" >
220- < p >
221- Custom text lets you type any text you want - code, prose, lyrics.
222- </ p >
223- < p >
224- Open the command line with{ " " }
225- < span class = "text-text" > Ctrl + Shift + P</ span > , search for{ " " }
226- < span class = "text-text" > custom text</ span > , and paste your content.
227- You can also set it to repeat or randomize word order.
228- </ p >
229- </ div >
230- ) ,
231- } ,
232- ] ;
23+ type FaqData = {
24+ topics : FaqTopic [ ] ;
25+ } ;
26+
27+ async function getFaqData ( ) : Promise < FaqData > {
28+ return cachedFetchJson < FaqData > ( "/faq.json" ) ;
29+ }
30+
31+ function RenderContent ( props : { blocks : FaqContentBlock [ ] } ) : JSXElement {
32+ return (
33+ < div class = "flex flex-col gap-4" >
34+ < For each = { props . blocks } >
35+ { ( block ) => (
36+ < Show
37+ when = { block . type === "list" }
38+ fallback = {
39+ < p > { ( block as { type : "paragraph" ; text : string } ) . text } </ p >
40+ }
41+ >
42+ < ul class = "flex list-disc flex-col gap-2 pl-4" >
43+ < For each = { ( block as { type : "list" ; items : string [ ] } ) . items } >
44+ { ( item ) => < li > { item } </ li > }
45+ </ For >
46+ </ ul >
47+ </ Show >
48+ ) }
49+ </ For >
50+ </ div >
51+ ) ;
52+ }
23353
23454export function FaqModal ( ) : JSXElement {
23555 const [ selectedIndex , setSelectedIndex ] = createSignal ( 0 ) ;
23656 const [ search , setSearch ] = createSignal ( "" ) ;
57+ const [ faqData ] = createResource ( getFaqData ) ;
23758
23859 const filteredTopics = createMemo ( ( ) => {
60+ const topics = faqData ( ) ?. topics ?? [ ] ;
23961 const q = search ( ) . toLowerCase ( ) . trim ( ) ;
240- if ( q === "" ) return faqTopics . map ( ( t , i ) => ( { ...t , originalIndex : i } ) ) ;
241- return faqTopics
62+ if ( q === "" ) return topics . map ( ( t , i ) => ( { ...t , originalIndex : i } ) ) ;
63+ return topics
24264 . map ( ( t , i ) => ( { ...t , originalIndex : i } ) )
24365 . filter (
24466 ( t ) =>
@@ -269,7 +91,7 @@ export function FaqModal(): JSXElement {
26991 < input
27092 type = "text"
27193 placeholder = "Search..."
272- class = "w-full rounded bg-sub-alt px-3 py-2 text-sm text-text placeholder-sub outline-none focus-visible:shadow-none"
94+ class = "w-full rounded bg-sub-alt px-3 py-2 text-sm text-text placeholder-sub [color-scheme:dark] outline-none focus-visible:shadow-none"
27395 value = { search ( ) }
27496 onInput = { ( e ) => {
27597 setSearch ( e . currentTarget . value ) ;
@@ -278,33 +100,40 @@ export function FaqModal(): JSXElement {
278100 < div class = "grid min-h-0 flex-1 grid-cols-[12rem_1fr] gap-4 overflow-hidden" >
279101 < div class = "flex min-h-0 flex-col gap-1 overflow-y-auto pr-2" >
280102 < Show
281- when = { filteredTopics ( ) . length > 0 }
282- fallback = {
283- < div class = "p-2 text-sm text-sub" > No results found.</ div >
284- }
103+ when = { ! faqData . loading }
104+ fallback = { < div class = "p-2 text-sm text-sub" > Loading...</ div > }
285105 >
286- < For each = { filteredTopics ( ) } >
287- { ( topic ) => (
288- < button
289- type = "button"
290- class = "rounded p-2 text-left text-sm transition-colors"
291- classList = { {
292- "bg-text text-bg" :
293- effectiveIndex ( ) === topic . originalIndex ,
294- "text-sub hover:text-bg hover:bg-text" :
295- effectiveIndex ( ) !== topic . originalIndex ,
296- } }
297- onClick = { ( ) => setSelectedIndex ( topic . originalIndex ) }
298- >
299- { topic . title }
300- </ button >
301- ) }
302- </ For >
106+ < Show
107+ when = { filteredTopics ( ) . length > 0 }
108+ fallback = {
109+ < div class = "p-2 text-sm text-sub" > No results found.</ div >
110+ }
111+ >
112+ < For each = { filteredTopics ( ) } >
113+ { ( topic ) => (
114+ < button
115+ type = "button"
116+ class = "rounded p-2 text-left text-sm transition-colors"
117+ classList = { {
118+ "bg-text text-bg" :
119+ effectiveIndex ( ) === topic . originalIndex ,
120+ "text-sub hover:text-bg hover:bg-text" :
121+ effectiveIndex ( ) !== topic . originalIndex ,
122+ } }
123+ onClick = { ( ) => setSelectedIndex ( topic . originalIndex ) }
124+ >
125+ { topic . title }
126+ </ button >
127+ ) }
128+ </ For >
129+ </ Show >
303130 </ Show >
304131 </ div >
305132 < div class = "overflow-y-auto text-sm text-sub" >
306- < Show when = { effectiveIndex ( ) !== - 1 } >
307- { faqTopics [ effectiveIndex ( ) ] ?. content }
133+ < Show when = { effectiveIndex ( ) !== - 1 && ! faqData . loading } >
134+ < RenderContent
135+ blocks = { faqData ( ) ?. topics [ effectiveIndex ( ) ] ?. content ?? [ ] }
136+ />
308137 </ Show >
309138 </ div >
310139 </ div >
0 commit comments