ligarb 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,6 +16,11 @@
16
16
  <%= custom_css %>
17
17
  </style>
18
18
  <%- end -%>
19
+ <%- if feedback -%>
20
+ <style>
21
+ <%= feedback[:css] %>
22
+ </style>
23
+ <%- end -%>
19
24
  <%- if assets.need?(:highlight) -%>
20
25
  <link rel="stylesheet" href="css/highlight.css">
21
26
  <%- end -%>
@@ -28,34 +33,151 @@
28
33
  <div class="sidebar" id="sidebar">
29
34
  <div class="sidebar-header">
30
35
  <div class="sidebar-header-top">
36
+ <%- if multilang -%>
37
+ <%- langs.each do |ld| -%>
38
+ <%- ld_cover = ld[:chapters].find(&:cover?) -%>
39
+ <%- if ld_cover -%>
40
+ <h1 class="book-title lang-content" data-lang="<%= ld[:lang] %>"><a href="#<%= ld_cover.slug %>" onclick="showChapter('<%= ld_cover.slug %>'); return false;"><%= h(ld[:title]) %></a></h1>
41
+ <%- else -%>
42
+ <h1 class="book-title lang-content" data-lang="<%= ld[:lang] %>"><%= h(ld[:title]) %></h1>
43
+ <%- end -%>
44
+ <%- end -%>
45
+ <%- else -%>
31
46
  <%- cover_chapter = chapters.find(&:cover?) -%>
32
47
  <%- if cover_chapter -%>
33
48
  <h1 class="book-title"><a href="#<%= cover_chapter.slug %>" onclick="showChapter('<%= cover_chapter.slug %>'); return false;"><%= h(title) %></a></h1>
34
49
  <%- else -%>
35
50
  <h1 class="book-title"><%= h(title) %></h1>
36
51
  <%- end -%>
52
+ <%- end -%>
37
53
  <button class="theme-toggle" id="theme-toggle" aria-label="Toggle dark mode" title="Toggle dark mode">&#9790;</button>
38
54
  </div>
55
+ <%- if multilang -%>
56
+ <%- langs.each do |ld| -%>
57
+ <%- unless ld[:author].empty? -%>
58
+ <p class="book-author lang-content" data-lang="<%= ld[:lang] %>"><%= h(ld[:author]) %></p>
59
+ <%- end -%>
60
+ <%- end -%>
61
+ <%- else -%>
39
62
  <%- unless author.empty? -%>
40
63
  <p class="book-author"><%= h(author) %></p>
41
64
  <%- end -%>
65
+ <%- end -%>
66
+ <%- if multilang -%>
67
+ <%- if ai_generated -%>
68
+ <%- langs.each do |ld| -%>
69
+ <span class="ai-badge lang-content" data-lang="<%= ld[:lang] %>"><%= ld[:language] == 'ja' ? 'AI 生成' : 'AI Generated' %></span>
70
+ <%- end -%>
71
+ <%- end -%>
72
+ <%- else -%>
42
73
  <%- if ai_generated -%>
43
74
  <span class="ai-badge"><%= language == 'ja' ? 'AI 生成' : 'AI Generated' %></span>
44
75
  <%- end -%>
76
+ <%- end -%>
77
+ <%- if multilang -%>
78
+ <div class="lang-switcher">
79
+ <%- langs.each do |ld| -%>
80
+ <button class="lang-link" data-lang="<%= ld[:lang] %>" onclick="switchLang('<%= ld[:lang] %>')"><%= h(ld[:lang].upcase) %></button>
81
+ <%- end -%>
82
+ </div>
83
+ <%- end -%>
45
84
  </div>
46
85
  <div class="search-box">
47
86
  <input type="text" id="toc-search" placeholder="Search..." autocomplete="off">
48
87
  <button class="search-clear" id="search-clear" type="button" aria-label="Clear search">&times;</button>
49
88
  </div>
50
89
  <nav class="toc" id="toc">
90
+ <%- if multilang -%>
91
+ <%- langs.each do |ld| -%>
92
+ <ul class="lang-content" data-lang="<%= ld[:lang] %>">
93
+ <li class="toc-chapter" data-chapter="<%= ld[:lang] %>--__toc__">
94
+ <a href="#<%= ld[:lang] %>--__toc__" class="toc-h1" onclick="showChapter('<%= ld[:lang] %>--__toc__')"><%= ld[:language] == 'ja' ? '目次' : 'Contents' %></a>
95
+ </li>
96
+ <%- ld[:structure].each do |node| -%>
97
+ <%- case node.type -%>
98
+ <%- when :cover -%>
99
+ <%- when :chapter -%>
100
+ <li class="toc-chapter" data-chapter="<%= node.chapter.slug %>">
101
+ <a href="#<%= node.chapter.slug %>" class="toc-h1" onclick="showChapter('<%= node.chapter.slug %>')"><%= h(node.chapter.display_title) %></a>
102
+ <%- sub_headings = node.chapter.headings.select { |h| h.level == 2 } -%>
103
+ <%- unless sub_headings.empty? -%>
104
+ <ul>
105
+ <%- sub_headings.each do |heading| -%>
106
+ <li>
107
+ <a href="#<%= node.chapter.slug %>--<%= heading.id %>" class="toc-h<%= heading.level %>" onclick="showChapterAndScroll('<%= node.chapter.slug %>', '<%= node.chapter.slug %>--<%= heading.id %>')"><%= h(heading.display_text) %></a>
108
+ </li>
109
+ <%- end -%>
110
+ </ul>
111
+ <%- end -%>
112
+ </li>
113
+ <%- when :part -%>
114
+ <li class="toc-part">
115
+ <a href="#<%= node.chapter.slug %>" class="toc-part-title" onclick="showChapter('<%= node.chapter.slug %>')"><%= h(node.chapter.title) %></a>
116
+ <ul>
117
+ <%- (node.children || []).each do |child| -%>
118
+ <li class="toc-chapter" data-chapter="<%= child.chapter.slug %>">
119
+ <a href="#<%= child.chapter.slug %>" class="toc-h1" onclick="showChapter('<%= child.chapter.slug %>')"><%= h(child.chapter.display_title) %></a>
120
+ <%- sub_headings = child.chapter.headings.select { |h| h.level == 2 } -%>
121
+ <%- unless sub_headings.empty? -%>
122
+ <ul>
123
+ <%- sub_headings.each do |heading| -%>
124
+ <li>
125
+ <a href="#<%= child.chapter.slug %>--<%= heading.id %>" class="toc-h<%= heading.level %>" onclick="showChapterAndScroll('<%= child.chapter.slug %>', '<%= child.chapter.slug %>--<%= heading.id %>')"><%= h(heading.display_text) %></a>
126
+ </li>
127
+ <%- end -%>
128
+ </ul>
129
+ <%- end -%>
130
+ </li>
131
+ <%- end -%>
132
+ </ul>
133
+ </li>
134
+ <%- when :appendix_group -%>
135
+ <li class="toc-appendix">
136
+ <span class="toc-appendix-title"><%= h(ld[:appendix_label]) %></span>
137
+ <ul>
138
+ <%- (node.children || []).each do |child| -%>
139
+ <li class="toc-chapter" data-chapter="<%= child.chapter.slug %>">
140
+ <a href="#<%= child.chapter.slug %>" class="toc-h1" onclick="showChapter('<%= child.chapter.slug %>')"><%= h(child.chapter.display_title) %></a>
141
+ <%- sub_headings = child.chapter.headings.select { |h| h.level == 2 } -%>
142
+ <%- unless sub_headings.empty? -%>
143
+ <ul>
144
+ <%- sub_headings.each do |heading| -%>
145
+ <li>
146
+ <a href="#<%= child.chapter.slug %>--<%= heading.id %>" class="toc-h<%= heading.level %>" onclick="showChapterAndScroll('<%= child.chapter.slug %>', '<%= child.chapter.slug %>--<%= heading.id %>')"><%= h(heading.display_text) %></a>
147
+ </li>
148
+ <%- end -%>
149
+ </ul>
150
+ <%- end -%>
151
+ </li>
152
+ <%- end -%>
153
+ </ul>
154
+ </li>
155
+ <%- end -%>
156
+ <%- end -%>
157
+ <%- unless ld[:bibliography].empty? -%>
158
+ <li class="toc-chapter" data-chapter="<%= ld[:lang] %>--__bibliography__">
159
+ <a href="#<%= ld[:lang] %>--__bibliography__" class="toc-h1" onclick="showChapter('<%= ld[:lang] %>--__bibliography__')"><%= ld[:language] == 'ja' ? '参考文献' : 'Bibliography' %></a>
160
+ </li>
161
+ <%- end -%>
162
+ <%- unless ld[:index_tree].empty? -%>
163
+ <li class="toc-chapter" data-chapter="<%= ld[:lang] %>--__index__">
164
+ <a href="#<%= ld[:lang] %>--__index__" class="toc-h1" onclick="showChapter('<%= ld[:lang] %>--__index__')"><%= ld[:language] == 'ja' ? '索引' : 'Index' %></a>
165
+ </li>
166
+ <%- end -%>
167
+ </ul>
168
+ <%- end -%>
169
+ <%- else -%>
51
170
  <ul>
171
+ <li class="toc-chapter" data-chapter="__toc__">
172
+ <a href="#__toc__" class="toc-h1" onclick="showChapter('__toc__')"><%= language == 'ja' ? '目次' : 'Contents' %></a>
173
+ </li>
52
174
  <%- structure.each do |node| -%>
53
175
  <%- case node.type -%>
54
176
  <%- when :cover -%>
55
177
  <%- when :chapter -%>
56
178
  <li class="toc-chapter" data-chapter="<%= node.chapter.slug %>">
57
179
  <a href="#<%= node.chapter.slug %>" class="toc-h1" onclick="showChapter('<%= node.chapter.slug %>')"><%= h(node.chapter.display_title) %></a>
58
- <%- sub_headings = node.chapter.headings.select { |h| h.level >= 2 } -%>
180
+ <%- sub_headings = node.chapter.headings.select { |h| h.level == 2 } -%>
59
181
  <%- unless sub_headings.empty? -%>
60
182
  <ul>
61
183
  <%- sub_headings.each do |heading| -%>
@@ -73,7 +195,7 @@
73
195
  <%- (node.children || []).each do |child| -%>
74
196
  <li class="toc-chapter" data-chapter="<%= child.chapter.slug %>">
75
197
  <a href="#<%= child.chapter.slug %>" class="toc-h1" onclick="showChapter('<%= child.chapter.slug %>')"><%= h(child.chapter.display_title) %></a>
76
- <%- sub_headings = child.chapter.headings.select { |h| h.level >= 2 } -%>
198
+ <%- sub_headings = child.chapter.headings.select { |h| h.level == 2 } -%>
77
199
  <%- unless sub_headings.empty? -%>
78
200
  <ul>
79
201
  <%- sub_headings.each do |heading| -%>
@@ -94,7 +216,7 @@
94
216
  <%- (node.children || []).each do |child| -%>
95
217
  <li class="toc-chapter" data-chapter="<%= child.chapter.slug %>">
96
218
  <a href="#<%= child.chapter.slug %>" class="toc-h1" onclick="showChapter('<%= child.chapter.slug %>')"><%= h(child.chapter.display_title) %></a>
97
- <%- sub_headings = child.chapter.headings.select { |h| h.level >= 2 } -%>
219
+ <%- sub_headings = child.chapter.headings.select { |h| h.level == 2 } -%>
98
220
  <%- unless sub_headings.empty? -%>
99
221
  <ul>
100
222
  <%- sub_headings.each do |heading| -%>
@@ -121,14 +243,188 @@
121
243
  </li>
122
244
  <%- end -%>
123
245
  </ul>
246
+ <%- end -%>
124
247
  </nav>
125
248
  </div>
126
249
 
127
250
  <button class="sidebar-toggle" id="sidebar-toggle" aria-label="Toggle sidebar">&#9776;</button>
128
251
 
129
252
  <main class="content" id="content">
130
- <%- chapters.each_with_index do |chapter, idx| -%>
131
- <section class="<%= chapter.cover? ? 'chapter cover-page' : 'chapter' %>" id="chapter-<%= chapter.slug %>" style="display: none;">
253
+ <%- if multilang -%>
254
+ <%- langs.each do |ld| -%>
255
+ <%- ld[:chapters].select(&:cover?).each do |chapter| -%>
256
+ <section class="chapter cover-page" id="chapter-<%= chapter.slug %>" data-lang="<%= ld[:lang] %>" style="display: none;">
257
+ <%= chapter.html %>
258
+ </section>
259
+ <%- end -%>
260
+ <section class="chapter toc-page" id="chapter-<%= ld[:lang] %>--__toc__" data-lang="<%= ld[:lang] %>" style="display: none;">
261
+ <h1><%= ld[:language] == 'ja' ? '目次' : 'Contents' %></h1>
262
+ <nav class="toc-full">
263
+ <%- ld[:structure].each do |node| -%>
264
+ <%- case node.type -%>
265
+ <%- when :cover -%>
266
+ <%- when :chapter -%>
267
+ <div class="toc-full-chapter">
268
+ <a href="#<%= node.chapter.slug %>" onclick="showChapter('<%= node.chapter.slug %>'); return false;"><%= h(node.chapter.display_title) %></a>
269
+ <%- sub = node.chapter.headings.select { |h| h.level >= 2 } -%>
270
+ <%- unless sub.empty? -%>
271
+ <ul><%- sub.each do |heading| -%>
272
+ <li class="toc-full-h<%= heading.level %>"><a href="#<%= node.chapter.slug %>--<%= heading.id %>" onclick="showChapterAndScroll('<%= node.chapter.slug %>', '<%= node.chapter.slug %>--<%= heading.id %>'); return false;"><%= h(heading.display_text) %></a></li>
273
+ <%- end -%></ul>
274
+ <%- end -%>
275
+ </div>
276
+ <%- when :part -%>
277
+ <div class="toc-full-part">
278
+ <a href="#<%= node.chapter.slug %>" onclick="showChapter('<%= node.chapter.slug %>'); return false;" class="toc-full-part-title"><%= h(node.chapter.title) %></a>
279
+ <%- (node.children || []).each do |child| -%>
280
+ <div class="toc-full-chapter">
281
+ <a href="#<%= child.chapter.slug %>" onclick="showChapter('<%= child.chapter.slug %>'); return false;"><%= h(child.chapter.display_title) %></a>
282
+ <%- sub = child.chapter.headings.select { |h| h.level >= 2 } -%>
283
+ <%- unless sub.empty? -%>
284
+ <ul><%- sub.each do |heading| -%>
285
+ <li class="toc-full-h<%= heading.level %>"><a href="#<%= child.chapter.slug %>--<%= heading.id %>" onclick="showChapterAndScroll('<%= child.chapter.slug %>', '<%= child.chapter.slug %>--<%= heading.id %>'); return false;"><%= h(heading.display_text) %></a></li>
286
+ <%- end -%></ul>
287
+ <%- end -%>
288
+ </div>
289
+ <%- end -%>
290
+ </div>
291
+ <%- when :appendix_group -%>
292
+ <div class="toc-full-part">
293
+ <span class="toc-full-part-title"><%= h(ld[:appendix_label]) %></span>
294
+ <%- (node.children || []).each do |child| -%>
295
+ <div class="toc-full-chapter">
296
+ <a href="#<%= child.chapter.slug %>" onclick="showChapter('<%= child.chapter.slug %>'); return false;"><%= h(child.chapter.display_title) %></a>
297
+ <%- sub = child.chapter.headings.select { |h| h.level >= 2 } -%>
298
+ <%- unless sub.empty? -%>
299
+ <ul><%- sub.each do |heading| -%>
300
+ <li class="toc-full-h<%= heading.level %>"><a href="#<%= child.chapter.slug %>--<%= heading.id %>" onclick="showChapterAndScroll('<%= child.chapter.slug %>', '<%= child.chapter.slug %>--<%= heading.id %>'); return false;"><%= h(heading.display_text) %></a></li>
301
+ <%- end -%></ul>
302
+ <%- end -%>
303
+ </div>
304
+ <%- end -%>
305
+ </div>
306
+ <%- end -%>
307
+ <%- end -%>
308
+ </nav>
309
+ </section>
310
+ <%- ld[:chapters].reject(&:cover?).each_with_index do |chapter, idx| -%>
311
+ <section class="chapter" id="chapter-<%= chapter.slug %>" data-lang="<%= ld[:lang] %>"<%= src_attrs(chapter, feedback) %> style="display: none;">
312
+ <%= chapter.html %>
313
+ <%- if ld[:repository] && !chapter.part_title? && !chapter.cover? -%>
314
+ <div class="edit-link">
315
+ <a href="<%= h(ld[:repository].chomp('/')) %>/blob/HEAD/<%= h(chapter.relative_path) %>" target="_blank" rel="noopener">View on GitHub</a>
316
+ </div>
317
+ <%- end -%>
318
+ <%- if ld[:footer] && !chapter.part_title? && !chapter.cover? -%>
319
+ <div class="chapter-footer"><%= h(ld[:footer]) %></div>
320
+ <%- end -%>
321
+ <%- unless chapter.cover? -%>
322
+ <nav class="chapter-nav">
323
+ <%- if idx > 0 -%>
324
+ <a href="#" class="nav-prev" onclick="showChapter('<%= ld[:chapters][idx-1].slug %>'); return false;">&larr; <%= h(ld[:chapters][idx-1].display_title) %></a>
325
+ <%- else -%>
326
+ <span></span>
327
+ <%- end -%>
328
+ <%- if idx < ld[:chapters].size - 1 -%>
329
+ <a href="#" class="nav-next" onclick="showChapter('<%= ld[:chapters][idx+1].slug %>'); return false;"><%= h(ld[:chapters][idx+1].display_title) %> &rarr;</a>
330
+ <%- end -%>
331
+ </nav>
332
+ <%- end -%>
333
+ </section>
334
+ <%- end -%>
335
+ <%- unless ld[:bibliography].empty? -%>
336
+ <section class="chapter bibliography-chapter" id="chapter-<%= ld[:lang] %>--__bibliography__" data-lang="<%= ld[:lang] %>" style="display: none;">
337
+ <h1><%= ld[:language] == 'ja' ? '参考文献' : 'Bibliography' %></h1>
338
+ <ul class="bibliography-list">
339
+ <%- ld[:bibliography].each do |entry| -%>
340
+ <li id="<%= ld[:lang] %>--bib-<%= h(entry[:key]) %>"><span class="bib-label">[<%= h(entry[:label]) %>]</span> <%= entry[:formatted_html] %></li>
341
+ <%- end -%>
342
+ </ul>
343
+ </section>
344
+ <%- end -%>
345
+ <%- unless ld[:index_tree].empty? -%>
346
+ <section class="chapter index-chapter" id="chapter-<%= ld[:lang] %>--__index__" data-lang="<%= ld[:lang] %>" style="display: none;">
347
+ <h1><%= ld[:language] == 'ja' ? '索引' : 'Index' %></h1>
348
+ <%- ld[:index_tree].keys.sort.each do |letter| -%>
349
+ <div class="index-group">
350
+ <h2 class="index-letter"><%= letter %></h2>
351
+ <dl class="index-entries">
352
+ <%- ld[:index_tree][letter].each do |item| -%>
353
+ <dt><%= h(item[:term]) %></dt>
354
+ <%- item[:refs].each do |ref| -%>
355
+ <dd><a href="#<%= ref[:anchor_id] %>" onclick="showChapterAndScroll('<%= ref[:chapter_slug] %>', '<%= ref[:anchor_id] %>'); return false;"><%= ref[:chapter_title] %></a></dd>
356
+ <%- end -%>
357
+ <%- item[:children].each do |child| -%>
358
+ <dt class="index-sub"><%= h(child[:term]) %></dt>
359
+ <%- child[:refs].each do |ref| -%>
360
+ <dd class="index-sub"><a href="#<%= ref[:anchor_id] %>" onclick="showChapterAndScroll('<%= ref[:chapter_slug] %>', '<%= ref[:anchor_id] %>'); return false;"><%= ref[:chapter_title] %></a></dd>
361
+ <%- end -%>
362
+ <%- end -%>
363
+ <%- end -%>
364
+ </dl>
365
+ </div>
366
+ <%- end -%>
367
+ </section>
368
+ <%- end -%>
369
+ <%- end -%>
370
+ <%- else -%>
371
+ <%- chapters.select(&:cover?).each do |chapter| -%>
372
+ <section class="chapter cover-page" id="chapter-<%= chapter.slug %>" style="display: none;">
373
+ <%= chapter.html %>
374
+ </section>
375
+ <%- end -%>
376
+ <section class="chapter toc-page" id="chapter-__toc__" style="display: none;">
377
+ <h1><%= language == 'ja' ? '目次' : 'Contents' %></h1>
378
+ <nav class="toc-full">
379
+ <%- structure.each do |node| -%>
380
+ <%- case node.type -%>
381
+ <%- when :cover -%>
382
+ <%- when :chapter -%>
383
+ <div class="toc-full-chapter">
384
+ <a href="#<%= node.chapter.slug %>" onclick="showChapter('<%= node.chapter.slug %>'); return false;"><%= h(node.chapter.display_title) %></a>
385
+ <%- sub = node.chapter.headings.select { |h| h.level >= 2 } -%>
386
+ <%- unless sub.empty? -%>
387
+ <ul><%- sub.each do |heading| -%>
388
+ <li class="toc-full-h<%= heading.level %>"><a href="#<%= node.chapter.slug %>--<%= heading.id %>" onclick="showChapterAndScroll('<%= node.chapter.slug %>', '<%= node.chapter.slug %>--<%= heading.id %>'); return false;"><%= h(heading.display_text) %></a></li>
389
+ <%- end -%></ul>
390
+ <%- end -%>
391
+ </div>
392
+ <%- when :part -%>
393
+ <div class="toc-full-part">
394
+ <a href="#<%= node.chapter.slug %>" onclick="showChapter('<%= node.chapter.slug %>'); return false;" class="toc-full-part-title"><%= h(node.chapter.title) %></a>
395
+ <%- (node.children || []).each do |child| -%>
396
+ <div class="toc-full-chapter">
397
+ <a href="#<%= child.chapter.slug %>" onclick="showChapter('<%= child.chapter.slug %>'); return false;"><%= h(child.chapter.display_title) %></a>
398
+ <%- sub = child.chapter.headings.select { |h| h.level >= 2 } -%>
399
+ <%- unless sub.empty? -%>
400
+ <ul><%- sub.each do |heading| -%>
401
+ <li class="toc-full-h<%= heading.level %>"><a href="#<%= child.chapter.slug %>--<%= heading.id %>" onclick="showChapterAndScroll('<%= child.chapter.slug %>', '<%= child.chapter.slug %>--<%= heading.id %>'); return false;"><%= h(heading.display_text) %></a></li>
402
+ <%- end -%></ul>
403
+ <%- end -%>
404
+ </div>
405
+ <%- end -%>
406
+ </div>
407
+ <%- when :appendix_group -%>
408
+ <div class="toc-full-part">
409
+ <span class="toc-full-part-title"><%= h(appendix_label) %></span>
410
+ <%- (node.children || []).each do |child| -%>
411
+ <div class="toc-full-chapter">
412
+ <a href="#<%= child.chapter.slug %>" onclick="showChapter('<%= child.chapter.slug %>'); return false;"><%= h(child.chapter.display_title) %></a>
413
+ <%- sub = child.chapter.headings.select { |h| h.level >= 2 } -%>
414
+ <%- unless sub.empty? -%>
415
+ <ul><%- sub.each do |heading| -%>
416
+ <li class="toc-full-h<%= heading.level %>"><a href="#<%= child.chapter.slug %>--<%= heading.id %>" onclick="showChapterAndScroll('<%= child.chapter.slug %>', '<%= child.chapter.slug %>--<%= heading.id %>'); return false;"><%= h(heading.display_text) %></a></li>
417
+ <%- end -%></ul>
418
+ <%- end -%>
419
+ </div>
420
+ <%- end -%>
421
+ </div>
422
+ <%- end -%>
423
+ <%- end -%>
424
+ </nav>
425
+ </section>
426
+ <%- chapters.reject(&:cover?).each_with_index do |chapter, idx| -%>
427
+ <section class="chapter" id="chapter-<%= chapter.slug %>"<%= src_attrs(chapter, feedback) %> style="display: none;">
132
428
  <%= chapter.html %>
133
429
  <%- if repository && !chapter.part_title? && !chapter.cover? -%>
134
430
  <div class="edit-link">
@@ -186,38 +482,182 @@
186
482
  <%- end -%>
187
483
  </section>
188
484
  <%- end -%>
485
+ <%- end -%>
189
486
  </main>
190
487
 
191
488
  <script>
192
489
  (function() {
193
- var chapters = [<%= chapters.map { |c| "\"#{c.slug}\"" }.join(", ") %><%= ', "__bibliography__"' unless bibliography.empty? %><%= ', "__index__"' unless index_tree.empty? %>];
490
+ <%- if multilang -%>
491
+ var langChapters = {
492
+ <%- langs.each do |ld| -%>
493
+ <%- ld_cover = ld[:chapters].find(&:cover?) -%>
494
+ "<%= ld[:lang] %>": [<%= "\"#{ld_cover.slug}\", " if ld_cover %>"<%= ld[:lang] %>--__toc__", <%= ld[:chapters].reject(&:cover?).map { |c| "\"#{c.slug}\"" }.join(", ") %><%= ", \"#{ld[:lang]}--__bibliography__\"" unless ld[:bibliography].empty? %><%= ", \"#{ld[:lang]}--__index__\"" unless ld[:index_tree].empty? %>],
495
+ <%- end -%>
496
+ };
497
+ var allLangs = [<%= langs.map { |ld| "\"#{ld[:lang]}\"" }.join(", ") %>];
498
+ var currentLang = localStorage.getItem('ligarb-lang') || allLangs[0];
499
+ if (allLangs.indexOf(currentLang) === -1) currentLang = allLangs[0];
500
+ var chapters = langChapters[currentLang];
501
+ <%- else -%>
502
+ <%- cover_ch = chapters.find(&:cover?) -%>
503
+ var chapters = [<%= "\"#{cover_ch.slug}\", " if cover_ch %>"__toc__", <%= chapters.reject(&:cover?).map { |c| "\"#{c.slug}\"" }.join(", ") %><%= ', "__bibliography__"' unless bibliography.empty? %><%= ', "__index__"' unless index_tree.empty? %>];
504
+ <%- end -%>
194
505
  var currentChapter = null;
195
506
 
196
507
  var navigating = false; // flag to suppress pushState during popstate/initial load
197
508
 
509
+ // Track the currently visible heading index and highlight TOC
510
+ var currentHeadingIndex = -1;
511
+ var headingObserver = null;
512
+
513
+ function setupHeadingObserver() {
514
+ if (headingObserver) headingObserver.disconnect();
515
+ if (!currentChapter) return;
516
+ var section = document.getElementById('chapter-' + currentChapter);
517
+ if (!section) return;
518
+ var headings = section.querySelectorAll('h1, h2, h3');
519
+ if (headings.length === 0) return;
520
+
521
+ headingObserver = new IntersectionObserver(function(entries) {
522
+ entries.forEach(function(entry) {
523
+ if (entry.isIntersecting) {
524
+ var idx = Array.prototype.indexOf.call(headings, entry.target);
525
+ if (idx !== -1) {
526
+ currentHeadingIndex = idx;
527
+ highlightTocHeading(entry.target);
528
+ }
529
+ }
530
+ });
531
+ }, { rootMargin: '0px 0px -70% 0px', threshold: 0 });
532
+
533
+ headings.forEach(function(h) { headingObserver.observe(h); });
534
+ }
535
+
536
+ function highlightTocHeading(heading) {
537
+ // Remove previous active-section highlights
538
+ document.querySelectorAll('.toc a.active-section').forEach(function(a) {
539
+ a.classList.remove('active-section');
540
+ });
541
+ // Find the TOC link matching this heading's ID
542
+ var hid = heading.id;
543
+ if (!hid) return;
544
+ var tocLink = document.querySelector('.toc a[href="#' + CSS.escape(hid) + '"]');
545
+ if (tocLink) tocLink.classList.add('active-section');
546
+ }
547
+
548
+ <%- if multilang -%>
549
+
550
+ function switchLang(lang) {
551
+ // Remember current position: chapter base slug and heading index
552
+ var oldLang = currentLang;
553
+ var targetSlug = null;
554
+ var targetHeadingIdx = currentHeadingIndex;
555
+
556
+ if (currentChapter) {
557
+ // Strip lang prefix to get the base slug (e.g. "ja--03-book-yml" → "03-book-yml")
558
+ var baseSlug = currentChapter.replace(new RegExp('^' + oldLang + '--'), '');
559
+ targetSlug = lang + '--' + baseSlug;
560
+ }
561
+
562
+ currentLang = lang;
563
+ chapters = langChapters[lang];
564
+ localStorage.setItem('ligarb-lang', lang);
565
+
566
+ // Toggle lang-content elements (TOC, header)
567
+ document.querySelectorAll('.lang-content').forEach(function(el) {
568
+ el.style.display = el.dataset.lang === lang ? '' : 'none';
569
+ });
570
+
571
+ // Toggle lang-switcher buttons
572
+ document.querySelectorAll('.lang-link').forEach(function(btn) {
573
+ if (btn.dataset.lang === lang) {
574
+ btn.classList.add('lang-current');
575
+ } else {
576
+ btn.classList.remove('lang-current');
577
+ }
578
+ });
579
+
580
+ // Hide all chapter sections, then show first of current lang
581
+ document.querySelectorAll('main .chapter').forEach(function(el) {
582
+ el.style.display = 'none';
583
+ });
584
+
585
+ // Reset search cache
586
+ chapterTextCache = null;
587
+
588
+ // Navigate to the corresponding chapter in the new language
589
+ if (targetSlug && chapters.indexOf(targetSlug) !== -1) {
590
+ showChapter(targetSlug);
591
+ // Scroll to the corresponding heading by index
592
+ if (targetHeadingIdx >= 0) {
593
+ setTimeout(function() {
594
+ var section = document.getElementById('chapter-' + targetSlug);
595
+ if (!section) return;
596
+ var headings = section.querySelectorAll('h1, h2, h3');
597
+ if (targetHeadingIdx < headings.length) {
598
+ headings[targetHeadingIdx].scrollIntoView({ behavior: 'smooth' });
599
+ }
600
+ }, 50);
601
+ }
602
+ } else {
603
+ showChapter(chapters[0]);
604
+ }
605
+ }
606
+
607
+ // Initialize lang display
608
+ function initLang() {
609
+ document.querySelectorAll('.lang-content').forEach(function(el) {
610
+ el.style.display = el.dataset.lang === currentLang ? '' : 'none';
611
+ });
612
+ document.querySelectorAll('.lang-link').forEach(function(btn) {
613
+ if (btn.dataset.lang === currentLang) btn.classList.add('lang-current');
614
+ });
615
+ }
616
+
617
+ window.switchLang = switchLang;
618
+ <%- end -%>
619
+
198
620
  function showChapter(slug, hash) {
199
621
  chapters.forEach(function(ch) {
200
622
  var el = document.getElementById('chapter-' + ch);
201
623
  if (el) el.style.display = (ch === slug) ? 'block' : 'none';
202
624
  });
203
625
 
204
- // Update active state in TOC
626
+ // Update active state in TOC and scroll sidebar to show active item
627
+ <%- if multilang -%>
628
+ var tocContainer = document.querySelector('.toc .lang-content[data-lang="' + currentLang + '"]');
629
+ var tocItems = tocContainer ? tocContainer.querySelectorAll('.toc-chapter') : [];
630
+ <%- else -%>
205
631
  var tocItems = document.querySelectorAll('.toc-chapter');
632
+ <%- end -%>
633
+ var activeItem = null;
206
634
  tocItems.forEach(function(item) {
207
635
  if (item.dataset.chapter === slug) {
208
636
  item.classList.add('active');
637
+ activeItem = item;
209
638
  } else {
210
639
  item.classList.remove('active');
211
640
  }
212
641
  });
642
+ if (activeItem) {
643
+ var tocNav = document.getElementById('toc');
644
+ var itemRect = activeItem.getBoundingClientRect();
645
+ var tocRect = tocNav.getBoundingClientRect();
646
+ if (itemRect.top < tocRect.top || itemRect.bottom > tocRect.bottom) {
647
+ activeItem.scrollIntoView({ block: 'center' });
648
+ }
649
+ }
213
650
 
214
651
  currentChapter = slug;
652
+ currentHeadingIndex = -1;
215
653
  var newHash = '#' + (hash || slug);
216
654
  if (!navigating) {
217
655
  history.pushState(null, '', newHash);
218
656
  }
219
657
  window.scrollTo(0, 0);
220
658
 
659
+ setupHeadingObserver();
660
+
221
661
  // Re-apply highlight if search is active
222
662
  if (searchInput && searchInput.value) {
223
663
  highlightContent(searchInput.value);
@@ -232,7 +672,21 @@
232
672
  if (!section) return;
233
673
  if (typeof mermaid !== 'undefined') {
234
674
  var unrendered = section.querySelectorAll('.mermaid:not([data-processed])');
235
- if (unrendered.length > 0) mermaid.run({nodes: unrendered});
675
+ if (unrendered.length > 0) {
676
+ // Save original source for error recovery
677
+ var sources = {};
678
+ unrendered.forEach(function(el) { sources[el.id || el.textContent.slice(0, 50)] = el.textContent; });
679
+ mermaid.run({nodes: unrendered, suppressErrors: true}).catch(function() {}).finally(function() {
680
+ unrendered.forEach(function(el) {
681
+ // mermaid sets data-processed; if it's still text-only (no SVG), it failed
682
+ if (!el.querySelector('svg')) {
683
+ var src = sources[el.id || el.textContent.slice(0, 50)] || el.textContent;
684
+ el.innerHTML = '<pre style="color:#c00;border:1px solid #c00;padding:0.5em;white-space:pre-wrap">mermaid error:\n' +
685
+ src.replace(/</g, '&lt;') + '</pre>';
686
+ }
687
+ });
688
+ });
689
+ }
236
690
  }
237
691
  if (typeof katex !== 'undefined') {
238
692
  section.querySelectorAll('.math-block[data-math]').forEach(function(el) {
@@ -325,6 +779,7 @@
325
779
  if (target) {
326
780
  target.scrollIntoView({ behavior: 'smooth' });
327
781
  highlightElement(target);
782
+ highlightTocHeading(target);
328
783
  }
329
784
  }, 50);
330
785
  }
@@ -345,6 +800,14 @@
345
800
  return;
346
801
  }
347
802
 
803
+ <%- if multilang -%>
804
+ // In multilang mode, detect language from hash prefix (e.g., "en--cover")
805
+ var langMatch = hash.match(/^([a-z]{2})--/);
806
+ if (langMatch && allLangs.indexOf(langMatch[1]) !== -1 && langMatch[1] !== currentLang) {
807
+ switchLang(langMatch[1]);
808
+ }
809
+ <%- end -%>
810
+
348
811
  // Check if hash matches a chapter slug directly
349
812
  if (chapters.indexOf(hash) !== -1) {
350
813
  showChapter(hash);
@@ -352,7 +815,20 @@
352
815
  return;
353
816
  }
354
817
 
355
- <%- unless bibliography.empty? -%>
818
+ <%- if multilang -%>
819
+ // Check for bibliography entry (bib-KEY)
820
+ var bibMatch = hash.match(/^(?:([a-z]{2})--)?bib-(.+)/);
821
+ if (bibMatch) {
822
+ var bibLang = bibMatch[1] || currentLang;
823
+ showChapter(bibLang + '--__bibliography__', hash);
824
+ setTimeout(function() {
825
+ var target = document.getElementById(hash);
826
+ if (target) target.scrollIntoView({ behavior: 'smooth' });
827
+ }, 50);
828
+ navigating = false;
829
+ return;
830
+ }
831
+ <%- elsif !bibliography.empty? -%>
356
832
  // Check for bibliography entry (bib-KEY)
357
833
  var bibMatch = hash.match(/^bib-(.+)/);
358
834
  if (bibMatch) {
@@ -364,7 +840,7 @@
364
840
  navigating = false;
365
841
  return;
366
842
  }
367
- <%- end -%>
843
+ <%- end -%>
368
844
 
369
845
  // Check for footnote links (fn: or fnref:)
370
846
  var fnMatch = hash.match(/^(?:fn|fnref):(.+?)--/);
@@ -382,12 +858,31 @@
382
858
  // Check for deep link (chapter--heading)
383
859
  var parts = hash.split('--');
384
860
  if (parts.length >= 2) {
861
+ <%- if multilang -%>
862
+ // In multilang, slug is "lang--chapter" so join first two parts
863
+ var chSlug = parts[0] + '--' + parts[1];
864
+ if (chapters.indexOf(chSlug) !== -1) {
865
+ showChapterAndScroll(chSlug, hash);
866
+ navigating = false;
867
+ return;
868
+ }
869
+ // Try 3 parts: "lang--chapter--heading" → chapter is "lang--chapter"
870
+ if (parts.length >= 3) {
871
+ chSlug = parts[0] + '--' + parts[1];
872
+ if (chapters.indexOf(chSlug) !== -1) {
873
+ showChapterAndScroll(chSlug, hash);
874
+ navigating = false;
875
+ return;
876
+ }
877
+ }
878
+ <%- else -%>
385
879
  var chSlug = parts[0];
386
880
  if (chapters.indexOf(chSlug) !== -1) {
387
881
  showChapterAndScroll(chSlug, hash);
388
882
  navigating = false;
389
883
  return;
390
884
  }
885
+ <%- end -%>
391
886
  }
392
887
 
393
888
  // Fallback
@@ -429,8 +924,14 @@
429
924
  // Remove existing match count badges
430
925
  document.querySelectorAll('.search-match-count').forEach(function(el) { el.remove(); });
431
926
 
927
+ <%- if multilang -%>
928
+ var tocContainer = document.querySelector('.toc .lang-content[data-lang="' + currentLang + '"]');
929
+ var items = tocContainer ? tocContainer.querySelectorAll('.toc-chapter') : [];
930
+ var partItems = tocContainer ? tocContainer.querySelectorAll('.toc-part, .toc-appendix') : [];
931
+ <%- else -%>
432
932
  var items = document.querySelectorAll('.toc-chapter');
433
933
  var partItems = document.querySelectorAll('.toc-part, .toc-appendix');
934
+ <%- end -%>
434
935
 
435
936
  if (!query || query.length < 2) {
436
937
  // Show all items when query is too short
@@ -597,6 +1098,9 @@
597
1098
  window.renderSpecialBlocks = function() { if (currentChapter) renderSpecialBlocks(currentChapter); };
598
1099
 
599
1100
  // Initialize (replace initial entry so first back goes to previous page, not same page)
1101
+ <%- if multilang -%>
1102
+ initLang();
1103
+ <%- end -%>
600
1104
  navigating = true;
601
1105
  handleHash();
602
1106
  history.replaceState(null, '', location.hash || '#' + (chapters[0] || ''));
@@ -624,5 +1128,11 @@ renderSpecialBlocks();
624
1128
  <script src="js/function-plot.min.js"></script>
625
1129
  <script>renderSpecialBlocks();</script>
626
1130
  <%- end -%>
1131
+ <%- if feedback -%>
1132
+ <script>window._ligarbReview = <%= feedback[:config_json] %>;</script>
1133
+ <script>
1134
+ <%= feedback[:js] %>
1135
+ </script>
1136
+ <%- end -%>
627
1137
  </body>
628
1138
  </html>