ligarb 0.5.0 → 0.7.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.
- checksums.yaml +4 -4
- data/assets/review.css +17 -0
- data/assets/review.js +5 -2
- data/assets/serve.js +33 -0
- data/assets/style.css +132 -0
- data/lib/ligarb/asset_manager.rb +17 -2
- data/lib/ligarb/builder.rb +82 -10
- data/lib/ligarb/chapter.rb +11 -5
- data/lib/ligarb/claude_runner.rb +140 -12
- data/lib/ligarb/cli.rb +161 -6
- data/lib/ligarb/config.rb +105 -2
- data/lib/ligarb/initializer.rb +20 -0
- data/lib/ligarb/review_store.rb +72 -50
- data/lib/ligarb/server.rb +145 -18
- data/lib/ligarb/template.rb +60 -0
- data/lib/ligarb/version.rb +1 -1
- data/lib/ligarb/writer.rb +26 -1
- data/templates/book.html.erb +598 -29
- metadata +2 -2
data/templates/book.html.erb
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html lang="<%= language %>">
|
|
2
|
+
<html lang="<%= h(language) %>">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title><%= title %></title>
|
|
6
|
+
<title><%= h(title) %></title>
|
|
7
7
|
<%- if ai_generated -%>
|
|
8
8
|
<meta name="robots" content="noindex, nofollow, noarchive">
|
|
9
9
|
<meta name="robots" content="noai, noimageai">
|
|
@@ -28,39 +28,156 @@
|
|
|
28
28
|
<div class="sidebar" id="sidebar">
|
|
29
29
|
<div class="sidebar-header">
|
|
30
30
|
<div class="sidebar-header-top">
|
|
31
|
+
<%- if multilang -%>
|
|
32
|
+
<%- langs.each do |ld| -%>
|
|
33
|
+
<%- ld_cover = ld[:chapters].find(&:cover?) -%>
|
|
34
|
+
<%- if ld_cover -%>
|
|
35
|
+
<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>
|
|
36
|
+
<%- else -%>
|
|
37
|
+
<h1 class="book-title lang-content" data-lang="<%= ld[:lang] %>"><%= h(ld[:title]) %></h1>
|
|
38
|
+
<%- end -%>
|
|
39
|
+
<%- end -%>
|
|
40
|
+
<%- else -%>
|
|
31
41
|
<%- cover_chapter = chapters.find(&:cover?) -%>
|
|
32
42
|
<%- if cover_chapter -%>
|
|
33
|
-
<h1 class="book-title"><a href="#<%= cover_chapter.slug %>" onclick="showChapter('<%= cover_chapter.slug %>'); return false;"><%= title %></a></h1>
|
|
43
|
+
<h1 class="book-title"><a href="#<%= cover_chapter.slug %>" onclick="showChapter('<%= cover_chapter.slug %>'); return false;"><%= h(title) %></a></h1>
|
|
34
44
|
<%- else -%>
|
|
35
|
-
<h1 class="book-title"><%= title %></h1>
|
|
45
|
+
<h1 class="book-title"><%= h(title) %></h1>
|
|
36
46
|
<%- end -%>
|
|
47
|
+
<%- end -%>
|
|
37
48
|
<button class="theme-toggle" id="theme-toggle" aria-label="Toggle dark mode" title="Toggle dark mode">☾</button>
|
|
38
49
|
</div>
|
|
50
|
+
<%- if multilang -%>
|
|
51
|
+
<%- langs.each do |ld| -%>
|
|
52
|
+
<%- unless ld[:author].empty? -%>
|
|
53
|
+
<p class="book-author lang-content" data-lang="<%= ld[:lang] %>"><%= h(ld[:author]) %></p>
|
|
54
|
+
<%- end -%>
|
|
55
|
+
<%- end -%>
|
|
56
|
+
<%- else -%>
|
|
39
57
|
<%- unless author.empty? -%>
|
|
40
|
-
<p class="book-author"><%= author %></p>
|
|
58
|
+
<p class="book-author"><%= h(author) %></p>
|
|
59
|
+
<%- end -%>
|
|
60
|
+
<%- end -%>
|
|
61
|
+
<%- if multilang -%>
|
|
62
|
+
<%- if ai_generated -%>
|
|
63
|
+
<%- langs.each do |ld| -%>
|
|
64
|
+
<span class="ai-badge lang-content" data-lang="<%= ld[:lang] %>"><%= ld[:language] == 'ja' ? 'AI 生成' : 'AI Generated' %></span>
|
|
41
65
|
<%- end -%>
|
|
66
|
+
<%- end -%>
|
|
67
|
+
<%- else -%>
|
|
42
68
|
<%- if ai_generated -%>
|
|
43
69
|
<span class="ai-badge"><%= language == 'ja' ? 'AI 生成' : 'AI Generated' %></span>
|
|
44
70
|
<%- end -%>
|
|
71
|
+
<%- end -%>
|
|
72
|
+
<%- if multilang -%>
|
|
73
|
+
<div class="lang-switcher">
|
|
74
|
+
<%- langs.each do |ld| -%>
|
|
75
|
+
<button class="lang-link" data-lang="<%= ld[:lang] %>" onclick="switchLang('<%= ld[:lang] %>')"><%= h(ld[:lang].upcase) %></button>
|
|
76
|
+
<%- end -%>
|
|
77
|
+
</div>
|
|
78
|
+
<%- end -%>
|
|
45
79
|
</div>
|
|
46
80
|
<div class="search-box">
|
|
47
81
|
<input type="text" id="toc-search" placeholder="Search..." autocomplete="off">
|
|
48
82
|
<button class="search-clear" id="search-clear" type="button" aria-label="Clear search">×</button>
|
|
49
83
|
</div>
|
|
50
84
|
<nav class="toc" id="toc">
|
|
85
|
+
<%- if multilang -%>
|
|
86
|
+
<%- langs.each do |ld| -%>
|
|
87
|
+
<ul class="lang-content" data-lang="<%= ld[:lang] %>">
|
|
88
|
+
<li class="toc-chapter" data-chapter="<%= ld[:lang] %>--__toc__">
|
|
89
|
+
<a href="#<%= ld[:lang] %>--__toc__" class="toc-h1" onclick="showChapter('<%= ld[:lang] %>--__toc__')"><%= ld[:language] == 'ja' ? '目次' : 'Contents' %></a>
|
|
90
|
+
</li>
|
|
91
|
+
<%- ld[:structure].each do |node| -%>
|
|
92
|
+
<%- case node.type -%>
|
|
93
|
+
<%- when :cover -%>
|
|
94
|
+
<%- when :chapter -%>
|
|
95
|
+
<li class="toc-chapter" data-chapter="<%= node.chapter.slug %>">
|
|
96
|
+
<a href="#<%= node.chapter.slug %>" class="toc-h1" onclick="showChapter('<%= node.chapter.slug %>')"><%= h(node.chapter.display_title) %></a>
|
|
97
|
+
<%- sub_headings = node.chapter.headings.select { |h| h.level == 2 } -%>
|
|
98
|
+
<%- unless sub_headings.empty? -%>
|
|
99
|
+
<ul>
|
|
100
|
+
<%- sub_headings.each do |heading| -%>
|
|
101
|
+
<li>
|
|
102
|
+
<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>
|
|
103
|
+
</li>
|
|
104
|
+
<%- end -%>
|
|
105
|
+
</ul>
|
|
106
|
+
<%- end -%>
|
|
107
|
+
</li>
|
|
108
|
+
<%- when :part -%>
|
|
109
|
+
<li class="toc-part">
|
|
110
|
+
<a href="#<%= node.chapter.slug %>" class="toc-part-title" onclick="showChapter('<%= node.chapter.slug %>')"><%= h(node.chapter.title) %></a>
|
|
111
|
+
<ul>
|
|
112
|
+
<%- (node.children || []).each do |child| -%>
|
|
113
|
+
<li class="toc-chapter" data-chapter="<%= child.chapter.slug %>">
|
|
114
|
+
<a href="#<%= child.chapter.slug %>" class="toc-h1" onclick="showChapter('<%= child.chapter.slug %>')"><%= h(child.chapter.display_title) %></a>
|
|
115
|
+
<%- sub_headings = child.chapter.headings.select { |h| h.level == 2 } -%>
|
|
116
|
+
<%- unless sub_headings.empty? -%>
|
|
117
|
+
<ul>
|
|
118
|
+
<%- sub_headings.each do |heading| -%>
|
|
119
|
+
<li>
|
|
120
|
+
<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>
|
|
121
|
+
</li>
|
|
122
|
+
<%- end -%>
|
|
123
|
+
</ul>
|
|
124
|
+
<%- end -%>
|
|
125
|
+
</li>
|
|
126
|
+
<%- end -%>
|
|
127
|
+
</ul>
|
|
128
|
+
</li>
|
|
129
|
+
<%- when :appendix_group -%>
|
|
130
|
+
<li class="toc-appendix">
|
|
131
|
+
<span class="toc-appendix-title"><%= h(ld[:appendix_label]) %></span>
|
|
132
|
+
<ul>
|
|
133
|
+
<%- (node.children || []).each do |child| -%>
|
|
134
|
+
<li class="toc-chapter" data-chapter="<%= child.chapter.slug %>">
|
|
135
|
+
<a href="#<%= child.chapter.slug %>" class="toc-h1" onclick="showChapter('<%= child.chapter.slug %>')"><%= h(child.chapter.display_title) %></a>
|
|
136
|
+
<%- sub_headings = child.chapter.headings.select { |h| h.level == 2 } -%>
|
|
137
|
+
<%- unless sub_headings.empty? -%>
|
|
138
|
+
<ul>
|
|
139
|
+
<%- sub_headings.each do |heading| -%>
|
|
140
|
+
<li>
|
|
141
|
+
<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>
|
|
142
|
+
</li>
|
|
143
|
+
<%- end -%>
|
|
144
|
+
</ul>
|
|
145
|
+
<%- end -%>
|
|
146
|
+
</li>
|
|
147
|
+
<%- end -%>
|
|
148
|
+
</ul>
|
|
149
|
+
</li>
|
|
150
|
+
<%- end -%>
|
|
151
|
+
<%- end -%>
|
|
152
|
+
<%- unless ld[:bibliography].empty? -%>
|
|
153
|
+
<li class="toc-chapter" data-chapter="<%= ld[:lang] %>--__bibliography__">
|
|
154
|
+
<a href="#<%= ld[:lang] %>--__bibliography__" class="toc-h1" onclick="showChapter('<%= ld[:lang] %>--__bibliography__')"><%= ld[:language] == 'ja' ? '参考文献' : 'Bibliography' %></a>
|
|
155
|
+
</li>
|
|
156
|
+
<%- end -%>
|
|
157
|
+
<%- unless ld[:index_tree].empty? -%>
|
|
158
|
+
<li class="toc-chapter" data-chapter="<%= ld[:lang] %>--__index__">
|
|
159
|
+
<a href="#<%= ld[:lang] %>--__index__" class="toc-h1" onclick="showChapter('<%= ld[:lang] %>--__index__')"><%= ld[:language] == 'ja' ? '索引' : 'Index' %></a>
|
|
160
|
+
</li>
|
|
161
|
+
<%- end -%>
|
|
162
|
+
</ul>
|
|
163
|
+
<%- end -%>
|
|
164
|
+
<%- else -%>
|
|
51
165
|
<ul>
|
|
166
|
+
<li class="toc-chapter" data-chapter="__toc__">
|
|
167
|
+
<a href="#__toc__" class="toc-h1" onclick="showChapter('__toc__')"><%= language == 'ja' ? '目次' : 'Contents' %></a>
|
|
168
|
+
</li>
|
|
52
169
|
<%- structure.each do |node| -%>
|
|
53
170
|
<%- case node.type -%>
|
|
54
171
|
<%- when :cover -%>
|
|
55
172
|
<%- when :chapter -%>
|
|
56
173
|
<li class="toc-chapter" data-chapter="<%= node.chapter.slug %>">
|
|
57
|
-
<a href="#<%= node.chapter.slug %>" class="toc-h1" onclick="showChapter('<%= node.chapter.slug %>')"><%= node.chapter.display_title %></a>
|
|
58
|
-
<%- sub_headings = node.chapter.headings.select { |h| h.level
|
|
174
|
+
<a href="#<%= node.chapter.slug %>" class="toc-h1" onclick="showChapter('<%= node.chapter.slug %>')"><%= h(node.chapter.display_title) %></a>
|
|
175
|
+
<%- sub_headings = node.chapter.headings.select { |h| h.level == 2 } -%>
|
|
59
176
|
<%- unless sub_headings.empty? -%>
|
|
60
177
|
<ul>
|
|
61
178
|
<%- sub_headings.each do |heading| -%>
|
|
62
179
|
<li>
|
|
63
|
-
<a href="#<%= node.chapter.slug %>--<%= heading.id %>" class="toc-h<%= heading.level %>" onclick="showChapterAndScroll('<%= node.chapter.slug %>', '<%= node.chapter.slug %>--<%= heading.id %>')"><%= heading.display_text %></a>
|
|
180
|
+
<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>
|
|
64
181
|
</li>
|
|
65
182
|
<%- end -%>
|
|
66
183
|
</ul>
|
|
@@ -68,17 +185,17 @@
|
|
|
68
185
|
</li>
|
|
69
186
|
<%- when :part -%>
|
|
70
187
|
<li class="toc-part">
|
|
71
|
-
<a href="#<%= node.chapter.slug %>" class="toc-part-title" onclick="showChapter('<%= node.chapter.slug %>')"><%= node.chapter.title %></a>
|
|
188
|
+
<a href="#<%= node.chapter.slug %>" class="toc-part-title" onclick="showChapter('<%= node.chapter.slug %>')"><%= h(node.chapter.title) %></a>
|
|
72
189
|
<ul>
|
|
73
190
|
<%- (node.children || []).each do |child| -%>
|
|
74
191
|
<li class="toc-chapter" data-chapter="<%= child.chapter.slug %>">
|
|
75
|
-
<a href="#<%= child.chapter.slug %>" class="toc-h1" onclick="showChapter('<%= child.chapter.slug %>')"><%= child.chapter.display_title %></a>
|
|
76
|
-
<%- sub_headings = child.chapter.headings.select { |h| h.level
|
|
192
|
+
<a href="#<%= child.chapter.slug %>" class="toc-h1" onclick="showChapter('<%= child.chapter.slug %>')"><%= h(child.chapter.display_title) %></a>
|
|
193
|
+
<%- sub_headings = child.chapter.headings.select { |h| h.level == 2 } -%>
|
|
77
194
|
<%- unless sub_headings.empty? -%>
|
|
78
195
|
<ul>
|
|
79
196
|
<%- sub_headings.each do |heading| -%>
|
|
80
197
|
<li>
|
|
81
|
-
<a href="#<%= child.chapter.slug %>--<%= heading.id %>" class="toc-h<%= heading.level %>" onclick="showChapterAndScroll('<%= child.chapter.slug %>', '<%= child.chapter.slug %>--<%= heading.id %>')"><%= heading.display_text %></a>
|
|
198
|
+
<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>
|
|
82
199
|
</li>
|
|
83
200
|
<%- end -%>
|
|
84
201
|
</ul>
|
|
@@ -89,17 +206,17 @@
|
|
|
89
206
|
</li>
|
|
90
207
|
<%- when :appendix_group -%>
|
|
91
208
|
<li class="toc-appendix">
|
|
92
|
-
<span class="toc-appendix-title"><%= appendix_label %></span>
|
|
209
|
+
<span class="toc-appendix-title"><%= h(appendix_label) %></span>
|
|
93
210
|
<ul>
|
|
94
211
|
<%- (node.children || []).each do |child| -%>
|
|
95
212
|
<li class="toc-chapter" data-chapter="<%= child.chapter.slug %>">
|
|
96
|
-
<a href="#<%= child.chapter.slug %>" class="toc-h1" onclick="showChapter('<%= child.chapter.slug %>')"><%= child.chapter.display_title %></a>
|
|
97
|
-
<%- sub_headings = child.chapter.headings.select { |h| h.level
|
|
213
|
+
<a href="#<%= child.chapter.slug %>" class="toc-h1" onclick="showChapter('<%= child.chapter.slug %>')"><%= h(child.chapter.display_title) %></a>
|
|
214
|
+
<%- sub_headings = child.chapter.headings.select { |h| h.level == 2 } -%>
|
|
98
215
|
<%- unless sub_headings.empty? -%>
|
|
99
216
|
<ul>
|
|
100
217
|
<%- sub_headings.each do |heading| -%>
|
|
101
218
|
<li>
|
|
102
|
-
<a href="#<%= child.chapter.slug %>--<%= heading.id %>" class="toc-h<%= heading.level %>" onclick="showChapterAndScroll('<%= child.chapter.slug %>', '<%= child.chapter.slug %>--<%= heading.id %>')"><%= heading.display_text %></a>
|
|
219
|
+
<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>
|
|
103
220
|
</li>
|
|
104
221
|
<%- end -%>
|
|
105
222
|
</ul>
|
|
@@ -121,32 +238,196 @@
|
|
|
121
238
|
</li>
|
|
122
239
|
<%- end -%>
|
|
123
240
|
</ul>
|
|
241
|
+
<%- end -%>
|
|
124
242
|
</nav>
|
|
125
243
|
</div>
|
|
126
244
|
|
|
127
245
|
<button class="sidebar-toggle" id="sidebar-toggle" aria-label="Toggle sidebar">☰</button>
|
|
128
246
|
|
|
129
247
|
<main class="content" id="content">
|
|
248
|
+
<%- if multilang -%>
|
|
249
|
+
<%- langs.each do |ld| -%>
|
|
250
|
+
<section class="chapter toc-page" id="chapter-<%= ld[:lang] %>--__toc__" data-lang="<%= ld[:lang] %>" style="display: none;">
|
|
251
|
+
<h1><%= ld[:language] == 'ja' ? '目次' : 'Contents' %></h1>
|
|
252
|
+
<nav class="toc-full">
|
|
253
|
+
<%- ld[:structure].each do |node| -%>
|
|
254
|
+
<%- case node.type -%>
|
|
255
|
+
<%- when :cover -%>
|
|
256
|
+
<%- when :chapter -%>
|
|
257
|
+
<div class="toc-full-chapter">
|
|
258
|
+
<a href="#<%= node.chapter.slug %>" onclick="showChapter('<%= node.chapter.slug %>'); return false;"><%= h(node.chapter.display_title) %></a>
|
|
259
|
+
<%- sub = node.chapter.headings.select { |h| h.level >= 2 } -%>
|
|
260
|
+
<%- unless sub.empty? -%>
|
|
261
|
+
<ul><%- sub.each do |heading| -%>
|
|
262
|
+
<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>
|
|
263
|
+
<%- end -%></ul>
|
|
264
|
+
<%- end -%>
|
|
265
|
+
</div>
|
|
266
|
+
<%- when :part -%>
|
|
267
|
+
<div class="toc-full-part">
|
|
268
|
+
<a href="#<%= node.chapter.slug %>" onclick="showChapter('<%= node.chapter.slug %>'); return false;" class="toc-full-part-title"><%= h(node.chapter.title) %></a>
|
|
269
|
+
<%- (node.children || []).each do |child| -%>
|
|
270
|
+
<div class="toc-full-chapter">
|
|
271
|
+
<a href="#<%= child.chapter.slug %>" onclick="showChapter('<%= child.chapter.slug %>'); return false;"><%= h(child.chapter.display_title) %></a>
|
|
272
|
+
<%- sub = child.chapter.headings.select { |h| h.level >= 2 } -%>
|
|
273
|
+
<%- unless sub.empty? -%>
|
|
274
|
+
<ul><%- sub.each do |heading| -%>
|
|
275
|
+
<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>
|
|
276
|
+
<%- end -%></ul>
|
|
277
|
+
<%- end -%>
|
|
278
|
+
</div>
|
|
279
|
+
<%- end -%>
|
|
280
|
+
</div>
|
|
281
|
+
<%- when :appendix_group -%>
|
|
282
|
+
<div class="toc-full-part">
|
|
283
|
+
<span class="toc-full-part-title"><%= h(ld[:appendix_label]) %></span>
|
|
284
|
+
<%- (node.children || []).each do |child| -%>
|
|
285
|
+
<div class="toc-full-chapter">
|
|
286
|
+
<a href="#<%= child.chapter.slug %>" onclick="showChapter('<%= child.chapter.slug %>'); return false;"><%= h(child.chapter.display_title) %></a>
|
|
287
|
+
<%- sub = child.chapter.headings.select { |h| h.level >= 2 } -%>
|
|
288
|
+
<%- unless sub.empty? -%>
|
|
289
|
+
<ul><%- sub.each do |heading| -%>
|
|
290
|
+
<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>
|
|
291
|
+
<%- end -%></ul>
|
|
292
|
+
<%- end -%>
|
|
293
|
+
</div>
|
|
294
|
+
<%- end -%>
|
|
295
|
+
</div>
|
|
296
|
+
<%- end -%>
|
|
297
|
+
<%- end -%>
|
|
298
|
+
</nav>
|
|
299
|
+
</section>
|
|
300
|
+
<%- ld[:chapters].each_with_index do |chapter, idx| -%>
|
|
301
|
+
<section class="<%= chapter.cover? ? 'chapter cover-page' : 'chapter' %>" id="chapter-<%= chapter.slug %>" data-lang="<%= ld[:lang] %>" style="display: none;">
|
|
302
|
+
<%= chapter.html %>
|
|
303
|
+
<%- if ld[:repository] && !chapter.part_title? && !chapter.cover? -%>
|
|
304
|
+
<div class="edit-link">
|
|
305
|
+
<a href="<%= h(ld[:repository].chomp('/')) %>/blob/HEAD/<%= h(chapter.relative_path) %>" target="_blank" rel="noopener">View on GitHub</a>
|
|
306
|
+
</div>
|
|
307
|
+
<%- end -%>
|
|
308
|
+
<%- if ld[:footer] && !chapter.part_title? && !chapter.cover? -%>
|
|
309
|
+
<div class="chapter-footer"><%= h(ld[:footer]) %></div>
|
|
310
|
+
<%- end -%>
|
|
311
|
+
<%- unless chapter.cover? -%>
|
|
312
|
+
<nav class="chapter-nav">
|
|
313
|
+
<%- if idx > 0 -%>
|
|
314
|
+
<a href="#" class="nav-prev" onclick="showChapter('<%= ld[:chapters][idx-1].slug %>'); return false;">← <%= h(ld[:chapters][idx-1].display_title) %></a>
|
|
315
|
+
<%- else -%>
|
|
316
|
+
<span></span>
|
|
317
|
+
<%- end -%>
|
|
318
|
+
<%- if idx < ld[:chapters].size - 1 -%>
|
|
319
|
+
<a href="#" class="nav-next" onclick="showChapter('<%= ld[:chapters][idx+1].slug %>'); return false;"><%= h(ld[:chapters][idx+1].display_title) %> →</a>
|
|
320
|
+
<%- end -%>
|
|
321
|
+
</nav>
|
|
322
|
+
<%- end -%>
|
|
323
|
+
</section>
|
|
324
|
+
<%- end -%>
|
|
325
|
+
<%- unless ld[:bibliography].empty? -%>
|
|
326
|
+
<section class="chapter bibliography-chapter" id="chapter-<%= ld[:lang] %>--__bibliography__" data-lang="<%= ld[:lang] %>" style="display: none;">
|
|
327
|
+
<h1><%= ld[:language] == 'ja' ? '参考文献' : 'Bibliography' %></h1>
|
|
328
|
+
<ul class="bibliography-list">
|
|
329
|
+
<%- ld[:bibliography].each do |entry| -%>
|
|
330
|
+
<li id="<%= ld[:lang] %>--bib-<%= h(entry[:key]) %>"><span class="bib-label">[<%= h(entry[:label]) %>]</span> <%= entry[:formatted_html] %></li>
|
|
331
|
+
<%- end -%>
|
|
332
|
+
</ul>
|
|
333
|
+
</section>
|
|
334
|
+
<%- end -%>
|
|
335
|
+
<%- unless ld[:index_tree].empty? -%>
|
|
336
|
+
<section class="chapter index-chapter" id="chapter-<%= ld[:lang] %>--__index__" data-lang="<%= ld[:lang] %>" style="display: none;">
|
|
337
|
+
<h1><%= ld[:language] == 'ja' ? '索引' : 'Index' %></h1>
|
|
338
|
+
<%- ld[:index_tree].keys.sort.each do |letter| -%>
|
|
339
|
+
<div class="index-group">
|
|
340
|
+
<h2 class="index-letter"><%= letter %></h2>
|
|
341
|
+
<dl class="index-entries">
|
|
342
|
+
<%- ld[:index_tree][letter].each do |item| -%>
|
|
343
|
+
<dt><%= h(item[:term]) %></dt>
|
|
344
|
+
<%- item[:refs].each do |ref| -%>
|
|
345
|
+
<dd><a href="#<%= ref[:anchor_id] %>" onclick="showChapterAndScroll('<%= ref[:chapter_slug] %>', '<%= ref[:anchor_id] %>'); return false;"><%= ref[:chapter_title] %></a></dd>
|
|
346
|
+
<%- end -%>
|
|
347
|
+
<%- item[:children].each do |child| -%>
|
|
348
|
+
<dt class="index-sub"><%= h(child[:term]) %></dt>
|
|
349
|
+
<%- child[:refs].each do |ref| -%>
|
|
350
|
+
<dd class="index-sub"><a href="#<%= ref[:anchor_id] %>" onclick="showChapterAndScroll('<%= ref[:chapter_slug] %>', '<%= ref[:anchor_id] %>'); return false;"><%= ref[:chapter_title] %></a></dd>
|
|
351
|
+
<%- end -%>
|
|
352
|
+
<%- end -%>
|
|
353
|
+
<%- end -%>
|
|
354
|
+
</dl>
|
|
355
|
+
</div>
|
|
356
|
+
<%- end -%>
|
|
357
|
+
</section>
|
|
358
|
+
<%- end -%>
|
|
359
|
+
<%- end -%>
|
|
360
|
+
<%- else -%>
|
|
361
|
+
<section class="chapter toc-page" id="chapter-__toc__" style="display: none;">
|
|
362
|
+
<h1><%= language == 'ja' ? '目次' : 'Contents' %></h1>
|
|
363
|
+
<nav class="toc-full">
|
|
364
|
+
<%- structure.each do |node| -%>
|
|
365
|
+
<%- case node.type -%>
|
|
366
|
+
<%- when :cover -%>
|
|
367
|
+
<%- when :chapter -%>
|
|
368
|
+
<div class="toc-full-chapter">
|
|
369
|
+
<a href="#<%= node.chapter.slug %>" onclick="showChapter('<%= node.chapter.slug %>'); return false;"><%= h(node.chapter.display_title) %></a>
|
|
370
|
+
<%- sub = node.chapter.headings.select { |h| h.level >= 2 } -%>
|
|
371
|
+
<%- unless sub.empty? -%>
|
|
372
|
+
<ul><%- sub.each do |heading| -%>
|
|
373
|
+
<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>
|
|
374
|
+
<%- end -%></ul>
|
|
375
|
+
<%- end -%>
|
|
376
|
+
</div>
|
|
377
|
+
<%- when :part -%>
|
|
378
|
+
<div class="toc-full-part">
|
|
379
|
+
<a href="#<%= node.chapter.slug %>" onclick="showChapter('<%= node.chapter.slug %>'); return false;" class="toc-full-part-title"><%= h(node.chapter.title) %></a>
|
|
380
|
+
<%- (node.children || []).each do |child| -%>
|
|
381
|
+
<div class="toc-full-chapter">
|
|
382
|
+
<a href="#<%= child.chapter.slug %>" onclick="showChapter('<%= child.chapter.slug %>'); return false;"><%= h(child.chapter.display_title) %></a>
|
|
383
|
+
<%- sub = child.chapter.headings.select { |h| h.level >= 2 } -%>
|
|
384
|
+
<%- unless sub.empty? -%>
|
|
385
|
+
<ul><%- sub.each do |heading| -%>
|
|
386
|
+
<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>
|
|
387
|
+
<%- end -%></ul>
|
|
388
|
+
<%- end -%>
|
|
389
|
+
</div>
|
|
390
|
+
<%- end -%>
|
|
391
|
+
</div>
|
|
392
|
+
<%- when :appendix_group -%>
|
|
393
|
+
<div class="toc-full-part">
|
|
394
|
+
<span class="toc-full-part-title"><%= h(appendix_label) %></span>
|
|
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
|
+
<%- end -%>
|
|
408
|
+
<%- end -%>
|
|
409
|
+
</nav>
|
|
410
|
+
</section>
|
|
130
411
|
<%- chapters.each_with_index do |chapter, idx| -%>
|
|
131
412
|
<section class="<%= chapter.cover? ? 'chapter cover-page' : 'chapter' %>" id="chapter-<%= chapter.slug %>" style="display: none;">
|
|
132
413
|
<%= chapter.html %>
|
|
133
414
|
<%- if repository && !chapter.part_title? && !chapter.cover? -%>
|
|
134
415
|
<div class="edit-link">
|
|
135
|
-
<a href="<%= repository.chomp('/') %>/blob/HEAD/<%= chapter.relative_path %>" target="_blank" rel="noopener">View on GitHub</a>
|
|
416
|
+
<a href="<%= h(repository.chomp('/')) %>/blob/HEAD/<%= h(chapter.relative_path) %>" target="_blank" rel="noopener">View on GitHub</a>
|
|
136
417
|
</div>
|
|
137
418
|
<%- end -%>
|
|
138
419
|
<%- if footer && !chapter.part_title? && !chapter.cover? -%>
|
|
139
|
-
<div class="chapter-footer"><%= footer %></div>
|
|
420
|
+
<div class="chapter-footer"><%= h(footer) %></div>
|
|
140
421
|
<%- end -%>
|
|
141
422
|
<%- unless chapter.cover? -%>
|
|
142
423
|
<nav class="chapter-nav">
|
|
143
424
|
<%- if idx > 0 -%>
|
|
144
|
-
<a href="#" class="nav-prev" onclick="showChapter('<%= chapters[idx-1].slug %>'); return false;">← <%= chapters[idx-1].display_title %></a>
|
|
425
|
+
<a href="#" class="nav-prev" onclick="showChapter('<%= chapters[idx-1].slug %>'); return false;">← <%= h(chapters[idx-1].display_title) %></a>
|
|
145
426
|
<%- else -%>
|
|
146
427
|
<span></span>
|
|
147
428
|
<%- end -%>
|
|
148
429
|
<%- if idx < chapters.size - 1 -%>
|
|
149
|
-
<a href="#" class="nav-next" onclick="showChapter('<%= chapters[idx+1].slug %>'); return false;"><%= chapters[idx+1].display_title %> →</a>
|
|
430
|
+
<a href="#" class="nav-next" onclick="showChapter('<%= chapters[idx+1].slug %>'); return false;"><%= h(chapters[idx+1].display_title) %> →</a>
|
|
150
431
|
<%- end -%>
|
|
151
432
|
</nav>
|
|
152
433
|
<%- end -%>
|
|
@@ -157,7 +438,7 @@
|
|
|
157
438
|
<h1><%= language == 'ja' ? '参考文献' : 'Bibliography' %></h1>
|
|
158
439
|
<ul class="bibliography-list">
|
|
159
440
|
<%- bibliography.each do |entry| -%>
|
|
160
|
-
<li id="bib-<%= entry[:key] %>"><span class="bib-label">[<%= entry[:label] %>]</span> <%= entry[:formatted_html] %></li>
|
|
441
|
+
<li id="bib-<%= h(entry[:key]) %>"><span class="bib-label">[<%= h(entry[:label]) %>]</span> <%= entry[:formatted_html] %></li>
|
|
161
442
|
<%- end -%>
|
|
162
443
|
</ul>
|
|
163
444
|
</section>
|
|
@@ -170,12 +451,12 @@
|
|
|
170
451
|
<h2 class="index-letter"><%= letter %></h2>
|
|
171
452
|
<dl class="index-entries">
|
|
172
453
|
<%- index_tree[letter].each do |item| -%>
|
|
173
|
-
<dt><%= item[:term] %></dt>
|
|
454
|
+
<dt><%= h(item[:term]) %></dt>
|
|
174
455
|
<%- item[:refs].each do |ref| -%>
|
|
175
456
|
<dd><a href="#<%= ref[:anchor_id] %>" onclick="showChapterAndScroll('<%= ref[:chapter_slug] %>', '<%= ref[:anchor_id] %>'); return false;"><%= ref[:chapter_title] %></a></dd>
|
|
176
457
|
<%- end -%>
|
|
177
458
|
<%- item[:children].each do |child| -%>
|
|
178
|
-
<dt class="index-sub"><%= child[:term] %></dt>
|
|
459
|
+
<dt class="index-sub"><%= h(child[:term]) %></dt>
|
|
179
460
|
<%- child[:refs].each do |ref| -%>
|
|
180
461
|
<dd class="index-sub"><a href="#<%= ref[:anchor_id] %>" onclick="showChapterAndScroll('<%= ref[:chapter_slug] %>', '<%= ref[:anchor_id] %>'); return false;"><%= ref[:chapter_title] %></a></dd>
|
|
181
462
|
<%- end -%>
|
|
@@ -186,38 +467,180 @@
|
|
|
186
467
|
<%- end -%>
|
|
187
468
|
</section>
|
|
188
469
|
<%- end -%>
|
|
470
|
+
<%- end -%>
|
|
189
471
|
</main>
|
|
190
472
|
|
|
191
473
|
<script>
|
|
192
474
|
(function() {
|
|
193
|
-
|
|
475
|
+
<%- if multilang -%>
|
|
476
|
+
var langChapters = {
|
|
477
|
+
<%- langs.each do |ld| -%>
|
|
478
|
+
"<%= ld[:lang] %>": ["<%= ld[:lang] %>--__toc__", <%= ld[:chapters].map { |c| "\"#{c.slug}\"" }.join(", ") %><%= ", \"#{ld[:lang]}--__bibliography__\"" unless ld[:bibliography].empty? %><%= ", \"#{ld[:lang]}--__index__\"" unless ld[:index_tree].empty? %>],
|
|
479
|
+
<%- end -%>
|
|
480
|
+
};
|
|
481
|
+
var allLangs = [<%= langs.map { |ld| "\"#{ld[:lang]}\"" }.join(", ") %>];
|
|
482
|
+
var currentLang = localStorage.getItem('ligarb-lang') || allLangs[0];
|
|
483
|
+
if (allLangs.indexOf(currentLang) === -1) currentLang = allLangs[0];
|
|
484
|
+
var chapters = langChapters[currentLang];
|
|
485
|
+
<%- else -%>
|
|
486
|
+
var chapters = ["__toc__", <%= chapters.map { |c| "\"#{c.slug}\"" }.join(", ") %><%= ', "__bibliography__"' unless bibliography.empty? %><%= ', "__index__"' unless index_tree.empty? %>];
|
|
487
|
+
<%- end -%>
|
|
194
488
|
var currentChapter = null;
|
|
195
489
|
|
|
196
490
|
var navigating = false; // flag to suppress pushState during popstate/initial load
|
|
197
491
|
|
|
492
|
+
// Track the currently visible heading index and highlight TOC
|
|
493
|
+
var currentHeadingIndex = -1;
|
|
494
|
+
var headingObserver = null;
|
|
495
|
+
|
|
496
|
+
function setupHeadingObserver() {
|
|
497
|
+
if (headingObserver) headingObserver.disconnect();
|
|
498
|
+
if (!currentChapter) return;
|
|
499
|
+
var section = document.getElementById('chapter-' + currentChapter);
|
|
500
|
+
if (!section) return;
|
|
501
|
+
var headings = section.querySelectorAll('h1, h2, h3');
|
|
502
|
+
if (headings.length === 0) return;
|
|
503
|
+
|
|
504
|
+
headingObserver = new IntersectionObserver(function(entries) {
|
|
505
|
+
entries.forEach(function(entry) {
|
|
506
|
+
if (entry.isIntersecting) {
|
|
507
|
+
var idx = Array.prototype.indexOf.call(headings, entry.target);
|
|
508
|
+
if (idx !== -1) {
|
|
509
|
+
currentHeadingIndex = idx;
|
|
510
|
+
highlightTocHeading(entry.target);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
}, { rootMargin: '0px 0px -70% 0px', threshold: 0 });
|
|
515
|
+
|
|
516
|
+
headings.forEach(function(h) { headingObserver.observe(h); });
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function highlightTocHeading(heading) {
|
|
520
|
+
// Remove previous active-section highlights
|
|
521
|
+
document.querySelectorAll('.toc a.active-section').forEach(function(a) {
|
|
522
|
+
a.classList.remove('active-section');
|
|
523
|
+
});
|
|
524
|
+
// Find the TOC link matching this heading's ID
|
|
525
|
+
var hid = heading.id;
|
|
526
|
+
if (!hid) return;
|
|
527
|
+
var tocLink = document.querySelector('.toc a[href="#' + CSS.escape(hid) + '"]');
|
|
528
|
+
if (tocLink) tocLink.classList.add('active-section');
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
<%- if multilang -%>
|
|
532
|
+
|
|
533
|
+
function switchLang(lang) {
|
|
534
|
+
// Remember current position: chapter base slug and heading index
|
|
535
|
+
var oldLang = currentLang;
|
|
536
|
+
var targetSlug = null;
|
|
537
|
+
var targetHeadingIdx = currentHeadingIndex;
|
|
538
|
+
|
|
539
|
+
if (currentChapter) {
|
|
540
|
+
// Strip lang prefix to get the base slug (e.g. "ja--03-book-yml" → "03-book-yml")
|
|
541
|
+
var baseSlug = currentChapter.replace(new RegExp('^' + oldLang + '--'), '');
|
|
542
|
+
targetSlug = lang + '--' + baseSlug;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
currentLang = lang;
|
|
546
|
+
chapters = langChapters[lang];
|
|
547
|
+
localStorage.setItem('ligarb-lang', lang);
|
|
548
|
+
|
|
549
|
+
// Toggle lang-content elements (TOC, header)
|
|
550
|
+
document.querySelectorAll('.lang-content').forEach(function(el) {
|
|
551
|
+
el.style.display = el.dataset.lang === lang ? '' : 'none';
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
// Toggle lang-switcher buttons
|
|
555
|
+
document.querySelectorAll('.lang-link').forEach(function(btn) {
|
|
556
|
+
if (btn.dataset.lang === lang) {
|
|
557
|
+
btn.classList.add('lang-current');
|
|
558
|
+
} else {
|
|
559
|
+
btn.classList.remove('lang-current');
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
// Hide all chapter sections, then show first of current lang
|
|
564
|
+
document.querySelectorAll('main .chapter').forEach(function(el) {
|
|
565
|
+
el.style.display = 'none';
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
// Reset search cache
|
|
569
|
+
chapterTextCache = null;
|
|
570
|
+
|
|
571
|
+
// Navigate to the corresponding chapter in the new language
|
|
572
|
+
if (targetSlug && chapters.indexOf(targetSlug) !== -1) {
|
|
573
|
+
showChapter(targetSlug);
|
|
574
|
+
// Scroll to the corresponding heading by index
|
|
575
|
+
if (targetHeadingIdx >= 0) {
|
|
576
|
+
setTimeout(function() {
|
|
577
|
+
var section = document.getElementById('chapter-' + targetSlug);
|
|
578
|
+
if (!section) return;
|
|
579
|
+
var headings = section.querySelectorAll('h1, h2, h3');
|
|
580
|
+
if (targetHeadingIdx < headings.length) {
|
|
581
|
+
headings[targetHeadingIdx].scrollIntoView({ behavior: 'smooth' });
|
|
582
|
+
}
|
|
583
|
+
}, 50);
|
|
584
|
+
}
|
|
585
|
+
} else {
|
|
586
|
+
showChapter(chapters[0]);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Initialize lang display
|
|
591
|
+
function initLang() {
|
|
592
|
+
document.querySelectorAll('.lang-content').forEach(function(el) {
|
|
593
|
+
el.style.display = el.dataset.lang === currentLang ? '' : 'none';
|
|
594
|
+
});
|
|
595
|
+
document.querySelectorAll('.lang-link').forEach(function(btn) {
|
|
596
|
+
if (btn.dataset.lang === currentLang) btn.classList.add('lang-current');
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
window.switchLang = switchLang;
|
|
601
|
+
<%- end -%>
|
|
602
|
+
|
|
198
603
|
function showChapter(slug, hash) {
|
|
199
604
|
chapters.forEach(function(ch) {
|
|
200
605
|
var el = document.getElementById('chapter-' + ch);
|
|
201
606
|
if (el) el.style.display = (ch === slug) ? 'block' : 'none';
|
|
202
607
|
});
|
|
203
608
|
|
|
204
|
-
// Update active state in TOC
|
|
609
|
+
// Update active state in TOC and scroll sidebar to show active item
|
|
610
|
+
<%- if multilang -%>
|
|
611
|
+
var tocContainer = document.querySelector('.toc .lang-content[data-lang="' + currentLang + '"]');
|
|
612
|
+
var tocItems = tocContainer ? tocContainer.querySelectorAll('.toc-chapter') : [];
|
|
613
|
+
<%- else -%>
|
|
205
614
|
var tocItems = document.querySelectorAll('.toc-chapter');
|
|
615
|
+
<%- end -%>
|
|
616
|
+
var activeItem = null;
|
|
206
617
|
tocItems.forEach(function(item) {
|
|
207
618
|
if (item.dataset.chapter === slug) {
|
|
208
619
|
item.classList.add('active');
|
|
620
|
+
activeItem = item;
|
|
209
621
|
} else {
|
|
210
622
|
item.classList.remove('active');
|
|
211
623
|
}
|
|
212
624
|
});
|
|
625
|
+
if (activeItem) {
|
|
626
|
+
var tocNav = document.getElementById('toc');
|
|
627
|
+
var itemRect = activeItem.getBoundingClientRect();
|
|
628
|
+
var tocRect = tocNav.getBoundingClientRect();
|
|
629
|
+
if (itemRect.top < tocRect.top || itemRect.bottom > tocRect.bottom) {
|
|
630
|
+
activeItem.scrollIntoView({ block: 'center' });
|
|
631
|
+
}
|
|
632
|
+
}
|
|
213
633
|
|
|
214
634
|
currentChapter = slug;
|
|
635
|
+
currentHeadingIndex = -1;
|
|
215
636
|
var newHash = '#' + (hash || slug);
|
|
216
637
|
if (!navigating) {
|
|
217
638
|
history.pushState(null, '', newHash);
|
|
218
639
|
}
|
|
219
640
|
window.scrollTo(0, 0);
|
|
220
641
|
|
|
642
|
+
setupHeadingObserver();
|
|
643
|
+
|
|
221
644
|
// Re-apply highlight if search is active
|
|
222
645
|
if (searchInput && searchInput.value) {
|
|
223
646
|
highlightContent(searchInput.value);
|
|
@@ -232,7 +655,21 @@
|
|
|
232
655
|
if (!section) return;
|
|
233
656
|
if (typeof mermaid !== 'undefined') {
|
|
234
657
|
var unrendered = section.querySelectorAll('.mermaid:not([data-processed])');
|
|
235
|
-
if (unrendered.length > 0)
|
|
658
|
+
if (unrendered.length > 0) {
|
|
659
|
+
// Save original source for error recovery
|
|
660
|
+
var sources = {};
|
|
661
|
+
unrendered.forEach(function(el) { sources[el.id || el.textContent.slice(0, 50)] = el.textContent; });
|
|
662
|
+
mermaid.run({nodes: unrendered, suppressErrors: true}).catch(function() {}).finally(function() {
|
|
663
|
+
unrendered.forEach(function(el) {
|
|
664
|
+
// mermaid sets data-processed; if it's still text-only (no SVG), it failed
|
|
665
|
+
if (!el.querySelector('svg')) {
|
|
666
|
+
var src = sources[el.id || el.textContent.slice(0, 50)] || el.textContent;
|
|
667
|
+
el.innerHTML = '<pre style="color:#c00;border:1px solid #c00;padding:0.5em;white-space:pre-wrap">mermaid error:\n' +
|
|
668
|
+
src.replace(/</g, '<') + '</pre>';
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
});
|
|
672
|
+
}
|
|
236
673
|
}
|
|
237
674
|
if (typeof katex !== 'undefined') {
|
|
238
675
|
section.querySelectorAll('.math-block[data-math]').forEach(function(el) {
|
|
@@ -248,16 +685,94 @@
|
|
|
248
685
|
}
|
|
249
686
|
});
|
|
250
687
|
}
|
|
688
|
+
if (typeof functionPlot !== 'undefined') {
|
|
689
|
+
section.querySelectorAll('.functionplot[data-plot]').forEach(function(el) {
|
|
690
|
+
if (el.childNodes.length > 0) return;
|
|
691
|
+
try {
|
|
692
|
+
var spec = el.getAttribute('data-plot');
|
|
693
|
+
var opts = parseFunctionPlotSpec(spec);
|
|
694
|
+
opts.target = el;
|
|
695
|
+
functionPlot(opts);
|
|
696
|
+
} catch(e) {
|
|
697
|
+
el.textContent = 'Error rendering plot: ' + e.message;
|
|
698
|
+
el.style.color = 'red';
|
|
699
|
+
}
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// Parse functionplot spec: lines of "y = expr" and "key: value" options
|
|
705
|
+
function parseFunctionPlotSpec(spec) {
|
|
706
|
+
var lines = spec.split('\n');
|
|
707
|
+
var fns = [];
|
|
708
|
+
var opts = {width: 600, height: 400};
|
|
709
|
+
lines.forEach(function(line) {
|
|
710
|
+
line = line.trim();
|
|
711
|
+
if (!line) return;
|
|
712
|
+
// Option lines: key: value
|
|
713
|
+
var optMatch = line.match(/^(xrange|yrange|range|width|height|title|grid)\s*:\s*(.+)$/i);
|
|
714
|
+
if (optMatch) {
|
|
715
|
+
var key = optMatch[1].toLowerCase();
|
|
716
|
+
var val = optMatch[2].trim();
|
|
717
|
+
if (key === 'range' || key === 'xrange') {
|
|
718
|
+
opts.xAxis = {domain: parseRange(val)};
|
|
719
|
+
} else if (key === 'yrange') {
|
|
720
|
+
opts.yAxis = {domain: parseRange(val)};
|
|
721
|
+
} else if (key === 'width') {
|
|
722
|
+
opts.width = parseInt(val, 10);
|
|
723
|
+
} else if (key === 'height') {
|
|
724
|
+
opts.height = parseInt(val, 10);
|
|
725
|
+
} else if (key === 'title') {
|
|
726
|
+
opts.title = val;
|
|
727
|
+
} else if (key === 'grid') {
|
|
728
|
+
opts.grid = val === 'true';
|
|
729
|
+
}
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
// Function lines: y = expr, r = expr (polar), x = expr (implicit), or bare expr
|
|
733
|
+
var fnMatch = line.match(/^y\s*=\s*(.+)$/);
|
|
734
|
+
if (fnMatch) { fns.push({fn: fnMatch[1].trim()}); return; }
|
|
735
|
+
var polarMatch = line.match(/^r\s*=\s*(.+)$/);
|
|
736
|
+
if (polarMatch) { fns.push({r: polarMatch[1].trim(), fnType: 'polar', graphType: 'polyline'}); return; }
|
|
737
|
+
var paramMatch = line.match(/^parametric\s*:\s*(.+?)\s*,\s*(.+)$/);
|
|
738
|
+
if (paramMatch) { fns.push({x: paramMatch[1].trim(), y: paramMatch[2].trim(), fnType: 'parametric', graphType: 'polyline'}); return; }
|
|
739
|
+
// Bare expression treated as y = expr
|
|
740
|
+
fns.push({fn: line});
|
|
741
|
+
});
|
|
742
|
+
opts.data = fns;
|
|
743
|
+
return opts;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
function parseRange(str) {
|
|
747
|
+
var m = str.match(/\[?\s*(-?[\d.eE+piPI*\/]+)\s*,\s*(-?[\d.eE+piPI*\/]+)\s*\]?/);
|
|
748
|
+
if (!m) return [-10, 10];
|
|
749
|
+
return [evalRangeNum(m[1]), evalRangeNum(m[2])];
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
function evalRangeNum(s) {
|
|
753
|
+
s = s.replace(/pi/gi, String(Math.PI));
|
|
754
|
+
try { return Function('"use strict"; return (' + s + ')')(); }
|
|
755
|
+
catch(e) { return parseFloat(s) || 0; }
|
|
251
756
|
}
|
|
252
757
|
|
|
253
758
|
function showChapterAndScroll(slug, headingId) {
|
|
254
759
|
showChapter(slug, headingId);
|
|
255
760
|
setTimeout(function() {
|
|
256
761
|
var target = document.getElementById(headingId);
|
|
257
|
-
if (target)
|
|
762
|
+
if (target) {
|
|
763
|
+
target.scrollIntoView({ behavior: 'smooth' });
|
|
764
|
+
highlightElement(target);
|
|
765
|
+
highlightTocHeading(target);
|
|
766
|
+
}
|
|
258
767
|
}, 50);
|
|
259
768
|
}
|
|
260
769
|
|
|
770
|
+
function highlightElement(el) {
|
|
771
|
+
var prev = document.querySelector('.ligarb-highlight');
|
|
772
|
+
if (prev) prev.classList.remove('ligarb-highlight');
|
|
773
|
+
el.classList.add('ligarb-highlight');
|
|
774
|
+
}
|
|
775
|
+
|
|
261
776
|
// Navigate to chapter/heading based on current URL hash
|
|
262
777
|
function handleHash() {
|
|
263
778
|
navigating = true;
|
|
@@ -268,6 +783,14 @@
|
|
|
268
783
|
return;
|
|
269
784
|
}
|
|
270
785
|
|
|
786
|
+
<%- if multilang -%>
|
|
787
|
+
// In multilang mode, detect language from hash prefix (e.g., "en--cover")
|
|
788
|
+
var langMatch = hash.match(/^([a-z]{2})--/);
|
|
789
|
+
if (langMatch && allLangs.indexOf(langMatch[1]) !== -1 && langMatch[1] !== currentLang) {
|
|
790
|
+
switchLang(langMatch[1]);
|
|
791
|
+
}
|
|
792
|
+
<%- end -%>
|
|
793
|
+
|
|
271
794
|
// Check if hash matches a chapter slug directly
|
|
272
795
|
if (chapters.indexOf(hash) !== -1) {
|
|
273
796
|
showChapter(hash);
|
|
@@ -275,7 +798,20 @@
|
|
|
275
798
|
return;
|
|
276
799
|
}
|
|
277
800
|
|
|
278
|
-
|
|
801
|
+
<%- if multilang -%>
|
|
802
|
+
// Check for bibliography entry (bib-KEY)
|
|
803
|
+
var bibMatch = hash.match(/^(?:([a-z]{2})--)?bib-(.+)/);
|
|
804
|
+
if (bibMatch) {
|
|
805
|
+
var bibLang = bibMatch[1] || currentLang;
|
|
806
|
+
showChapter(bibLang + '--__bibliography__', hash);
|
|
807
|
+
setTimeout(function() {
|
|
808
|
+
var target = document.getElementById(hash);
|
|
809
|
+
if (target) target.scrollIntoView({ behavior: 'smooth' });
|
|
810
|
+
}, 50);
|
|
811
|
+
navigating = false;
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
<%- elsif !bibliography.empty? -%>
|
|
279
815
|
// Check for bibliography entry (bib-KEY)
|
|
280
816
|
var bibMatch = hash.match(/^bib-(.+)/);
|
|
281
817
|
if (bibMatch) {
|
|
@@ -287,7 +823,7 @@
|
|
|
287
823
|
navigating = false;
|
|
288
824
|
return;
|
|
289
825
|
}
|
|
290
|
-
|
|
826
|
+
<%- end -%>
|
|
291
827
|
|
|
292
828
|
// Check for footnote links (fn: or fnref:)
|
|
293
829
|
var fnMatch = hash.match(/^(?:fn|fnref):(.+?)--/);
|
|
@@ -305,12 +841,31 @@
|
|
|
305
841
|
// Check for deep link (chapter--heading)
|
|
306
842
|
var parts = hash.split('--');
|
|
307
843
|
if (parts.length >= 2) {
|
|
844
|
+
<%- if multilang -%>
|
|
845
|
+
// In multilang, slug is "lang--chapter" so join first two parts
|
|
846
|
+
var chSlug = parts[0] + '--' + parts[1];
|
|
847
|
+
if (chapters.indexOf(chSlug) !== -1) {
|
|
848
|
+
showChapterAndScroll(chSlug, hash);
|
|
849
|
+
navigating = false;
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
// Try 3 parts: "lang--chapter--heading" → chapter is "lang--chapter"
|
|
853
|
+
if (parts.length >= 3) {
|
|
854
|
+
chSlug = parts[0] + '--' + parts[1];
|
|
855
|
+
if (chapters.indexOf(chSlug) !== -1) {
|
|
856
|
+
showChapterAndScroll(chSlug, hash);
|
|
857
|
+
navigating = false;
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
<%- else -%>
|
|
308
862
|
var chSlug = parts[0];
|
|
309
863
|
if (chapters.indexOf(chSlug) !== -1) {
|
|
310
864
|
showChapterAndScroll(chSlug, hash);
|
|
311
865
|
navigating = false;
|
|
312
866
|
return;
|
|
313
867
|
}
|
|
868
|
+
<%- end -%>
|
|
314
869
|
}
|
|
315
870
|
|
|
316
871
|
// Fallback
|
|
@@ -352,8 +907,14 @@
|
|
|
352
907
|
// Remove existing match count badges
|
|
353
908
|
document.querySelectorAll('.search-match-count').forEach(function(el) { el.remove(); });
|
|
354
909
|
|
|
910
|
+
<%- if multilang -%>
|
|
911
|
+
var tocContainer = document.querySelector('.toc .lang-content[data-lang="' + currentLang + '"]');
|
|
912
|
+
var items = tocContainer ? tocContainer.querySelectorAll('.toc-chapter') : [];
|
|
913
|
+
var partItems = tocContainer ? tocContainer.querySelectorAll('.toc-part, .toc-appendix') : [];
|
|
914
|
+
<%- else -%>
|
|
355
915
|
var items = document.querySelectorAll('.toc-chapter');
|
|
356
916
|
var partItems = document.querySelectorAll('.toc-part, .toc-appendix');
|
|
917
|
+
<%- end -%>
|
|
357
918
|
|
|
358
919
|
if (!query || query.length < 2) {
|
|
359
920
|
// Show all items when query is too short
|
|
@@ -520,6 +1081,9 @@
|
|
|
520
1081
|
window.renderSpecialBlocks = function() { if (currentChapter) renderSpecialBlocks(currentChapter); };
|
|
521
1082
|
|
|
522
1083
|
// Initialize (replace initial entry so first back goes to previous page, not same page)
|
|
1084
|
+
<%- if multilang -%>
|
|
1085
|
+
initLang();
|
|
1086
|
+
<%- end -%>
|
|
523
1087
|
navigating = true;
|
|
524
1088
|
handleHash();
|
|
525
1089
|
history.replaceState(null, '', location.hash || '#' + (chapters[0] || ''));
|
|
@@ -542,5 +1106,10 @@ renderSpecialBlocks();
|
|
|
542
1106
|
<script src="js/katex.min.js"></script>
|
|
543
1107
|
<script>renderSpecialBlocks();</script>
|
|
544
1108
|
<%- end -%>
|
|
1109
|
+
<%- if assets.need?(:functionplot) -%>
|
|
1110
|
+
<script src="js/d3.min.js"></script>
|
|
1111
|
+
<script src="js/function-plot.min.js"></script>
|
|
1112
|
+
<script>renderSpecialBlocks();</script>
|
|
1113
|
+
<%- end -%>
|
|
545
1114
|
</body>
|
|
546
1115
|
</html>
|