77
88const DEFAULT_BOOK_KEY = 'NCE1' ;
99const PLAY_MODE_STORAGE_KEY = 'playMode' ;
10+ const BOOK_SELECTION_STORAGE_KEY = 'selectedBookKey' ;
1011
1112const qs = ( selector , root = document ) => root . querySelector ( selector ) ;
1213const qsa = ( selector , root = document ) => Array . from ( root . querySelectorAll ( selector ) ) ;
1314const clamp = ( value , min , max ) => Math . min ( max , Math . max ( min , value ) ) ;
1415
15- // LRC 解析器
16- class LRCParser {
17- static parse ( lrcText ) {
18- const lines = lrcText . split ( '\n' ) ;
19- const lyrics = [ ] ;
20-
21- for ( const line of lines ) {
22- const match = line . match ( / \[ ( \d { 2 } ) : ( \d { 2 } ) \. ( \d { 2 , 3 } ) \] ( .+ ) / ) ;
23- if ( match ) {
24- const minutes = parseInt ( match [ 1 ] ) ;
25- const seconds = parseInt ( match [ 2 ] ) ;
26- const milliseconds = parseInt ( match [ 3 ] ) ;
27- const time = minutes * 60 + seconds + milliseconds / 1000 - 0.5 ;
28-
29- // 分割英文和中文(使用 | 分隔符)
30- const text = match [ 4 ] . trim ( ) ;
31- const parts = text . split ( '|' ) . map ( ( p ) => p . trim ( ) ) ;
32-
33- lyrics . push ( {
34- time,
35- english : parts [ 0 ] || '' ,
36- chinese : parts [ 1 ] || '' ,
37- fullText : text
38- } ) ;
39- }
40- }
41-
42- return lyrics . sort ( ( a , b ) => a . time - b . time ) ;
43- }
44- }
4516
4617class ReadingSystem {
4718 constructor ( ) {
@@ -129,8 +100,19 @@ class ReadingSystem {
129100 }
130101
131102 async applyBookFromHash ( ) {
132- const keyFromHash = location . hash . slice ( 1 ) . trim ( ) || DEFAULT_BOOK_KEY ;
133- await this . applyBookChange ( keyFromHash ) ;
103+ const keyFromHash = location . hash . slice ( 1 ) . trim ( ) ;
104+ const storedBookKey = this . loadBookPreference ( ) ;
105+ const initialBookKey = keyFromHash || storedBookKey || DEFAULT_BOOK_KEY ;
106+ await this . applyBookChange ( initialBookKey ) ;
107+ }
108+
109+ loadBookPreference ( ) {
110+ return localStorage . getItem ( BOOK_SELECTION_STORAGE_KEY ) ?. trim ( ) || '' ;
111+ }
112+
113+ persistBookPreference ( bookKey ) {
114+ if ( ! bookKey ) return ;
115+ localStorage . setItem ( BOOK_SELECTION_STORAGE_KEY , bookKey ) ;
134116 }
135117
136118 async applyBookChange ( bookKey ) {
@@ -146,6 +128,7 @@ class ReadingSystem {
146128
147129 this . state . bookKey = resolved . key || bookKey ;
148130 this . state . bookPath = resolved . bookPath . trim ( ) ;
131+ this . persistBookPreference ( this . state . bookKey ) ;
149132
150133 this . updateBookSelects ( ) ;
151134 await this . loadBookConfig ( ) ;
@@ -255,18 +238,20 @@ class ReadingSystem {
255238 ? clamp ( parsed , 0 , this . state . units . length - 1 )
256239 : 0 ;
257240
258- await this . loadUnitByIndex ( safeIndex ) ;
241+ await this . loadUnitByIndex ( safeIndex , { shouldScrollUnitIntoView : true } ) ;
259242 }
260243
261- async loadUnitByIndex ( unitIndex ) {
244+ async loadUnitByIndex ( unitIndex , options = { } ) {
245+ const { shouldScrollUnitIntoView = false } = options ;
246+
262247 this . state . currentUnitIndex = unitIndex ;
263248 localStorage . setItem ( `${ this . state . bookPath } /currentUnitIndex` , unitIndex ) ;
264249
265250 const unit = this . state . units [ unitIndex ] ;
266251 if ( ! unit ) return ;
267252
268253 this . resetPlayer ( ) ;
269- this . updateActiveUnit ( unitIndex ) ;
254+ this . updateActiveUnit ( unitIndex , { shouldScrollUnitIntoView } ) ;
270255 this . updateNavigationButtons ( ) ;
271256
272257 try {
@@ -313,7 +298,9 @@ class ReadingSystem {
313298 this . state . singlePlayEndTime = null ;
314299 }
315300
316- updateActiveUnit ( unitIndex ) {
301+ updateActiveUnit ( unitIndex , options = { } ) {
302+ const { shouldScrollUnitIntoView = false } = options ;
303+
317304 if ( this . dom . unitList ) {
318305 let activeItem = null ;
319306
@@ -326,7 +313,7 @@ class ReadingSystem {
326313 }
327314 } ) ;
328315
329- if ( activeItem ) {
316+ if ( activeItem && shouldScrollUnitIntoView ) {
330317 activeItem . scrollIntoView ( { block : 'center' , inline : 'nearest' } ) ;
331318 }
332319 }
@@ -943,3 +930,36 @@ function initSupportModal() {
943930 }
944931 } ) ;
945932}
933+
934+
935+
936+ // LRC 解析器
937+ class LRCParser {
938+ static parse ( lrcText ) {
939+ const lines = lrcText . split ( '\n' ) ;
940+ const lyrics = [ ] ;
941+
942+ for ( const line of lines ) {
943+ const match = line . match ( / \[ ( \d { 2 } ) : ( \d { 2 } ) \. ( \d { 2 , 3 } ) \] ( .+ ) / ) ;
944+ if ( match ) {
945+ const minutes = parseInt ( match [ 1 ] ) ;
946+ const seconds = parseInt ( match [ 2 ] ) ;
947+ const milliseconds = parseInt ( match [ 3 ] ) ;
948+ const time = minutes * 60 + seconds + milliseconds / 1000 - 0.5 ;
949+
950+ // 分割英文和中文(使用 | 分隔符)
951+ const text = match [ 4 ] . trim ( ) ;
952+ const parts = text . split ( '|' ) . map ( ( p ) => p . trim ( ) ) ;
953+
954+ lyrics . push ( {
955+ time,
956+ english : parts [ 0 ] || '' ,
957+ chinese : parts [ 1 ] || '' ,
958+ fullText : text
959+ } ) ;
960+ }
961+ }
962+
963+ return lyrics . sort ( ( a , b ) => a . time - b . time ) ;
964+ }
965+ }
0 commit comments