ligarb 0.5.0 → 0.6.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.
@@ -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">
@@ -30,14 +30,14 @@
30
30
  <div class="sidebar-header-top">
31
31
  <%- cover_chapter = chapters.find(&:cover?) -%>
32
32
  <%- if cover_chapter -%>
33
- <h1 class="book-title"><a href="#<%= cover_chapter.slug %>" onclick="showChapter('<%= cover_chapter.slug %>'); return false;"><%= title %></a></h1>
33
+ <h1 class="book-title"><a href="#<%= cover_chapter.slug %>" onclick="showChapter('<%= cover_chapter.slug %>'); return false;"><%= h(title) %></a></h1>
34
34
  <%- else -%>
35
- <h1 class="book-title"><%= title %></h1>
35
+ <h1 class="book-title"><%= h(title) %></h1>
36
36
  <%- end -%>
37
37
  <button class="theme-toggle" id="theme-toggle" aria-label="Toggle dark mode" title="Toggle dark mode">&#9790;</button>
38
38
  </div>
39
39
  <%- unless author.empty? -%>
40
- <p class="book-author"><%= author %></p>
40
+ <p class="book-author"><%= h(author) %></p>
41
41
  <%- end -%>
42
42
  <%- if ai_generated -%>
43
43
  <span class="ai-badge"><%= language == 'ja' ? 'AI 生成' : 'AI Generated' %></span>
@@ -54,13 +54,13 @@
54
54
  <%- when :cover -%>
55
55
  <%- when :chapter -%>
56
56
  <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>
57
+ <a href="#<%= node.chapter.slug %>" class="toc-h1" onclick="showChapter('<%= node.chapter.slug %>')"><%= h(node.chapter.display_title) %></a>
58
58
  <%- sub_headings = node.chapter.headings.select { |h| h.level >= 2 } -%>
59
59
  <%- unless sub_headings.empty? -%>
60
60
  <ul>
61
61
  <%- sub_headings.each do |heading| -%>
62
62
  <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>
63
+ <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
64
  </li>
65
65
  <%- end -%>
66
66
  </ul>
@@ -68,17 +68,17 @@
68
68
  </li>
69
69
  <%- when :part -%>
70
70
  <li class="toc-part">
71
- <a href="#<%= node.chapter.slug %>" class="toc-part-title" onclick="showChapter('<%= node.chapter.slug %>')"><%= node.chapter.title %></a>
71
+ <a href="#<%= node.chapter.slug %>" class="toc-part-title" onclick="showChapter('<%= node.chapter.slug %>')"><%= h(node.chapter.title) %></a>
72
72
  <ul>
73
73
  <%- (node.children || []).each do |child| -%>
74
74
  <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>
75
+ <a href="#<%= child.chapter.slug %>" class="toc-h1" onclick="showChapter('<%= child.chapter.slug %>')"><%= h(child.chapter.display_title) %></a>
76
76
  <%- sub_headings = child.chapter.headings.select { |h| h.level >= 2 } -%>
77
77
  <%- unless sub_headings.empty? -%>
78
78
  <ul>
79
79
  <%- sub_headings.each do |heading| -%>
80
80
  <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>
81
+ <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
82
  </li>
83
83
  <%- end -%>
84
84
  </ul>
@@ -89,17 +89,17 @@
89
89
  </li>
90
90
  <%- when :appendix_group -%>
91
91
  <li class="toc-appendix">
92
- <span class="toc-appendix-title"><%= appendix_label %></span>
92
+ <span class="toc-appendix-title"><%= h(appendix_label) %></span>
93
93
  <ul>
94
94
  <%- (node.children || []).each do |child| -%>
95
95
  <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>
96
+ <a href="#<%= child.chapter.slug %>" class="toc-h1" onclick="showChapter('<%= child.chapter.slug %>')"><%= h(child.chapter.display_title) %></a>
97
97
  <%- sub_headings = child.chapter.headings.select { |h| h.level >= 2 } -%>
98
98
  <%- unless sub_headings.empty? -%>
99
99
  <ul>
100
100
  <%- sub_headings.each do |heading| -%>
101
101
  <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>
102
+ <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
103
  </li>
104
104
  <%- end -%>
105
105
  </ul>
@@ -132,21 +132,21 @@
132
132
  <%= chapter.html %>
133
133
  <%- if repository && !chapter.part_title? && !chapter.cover? -%>
134
134
  <div class="edit-link">
135
- <a href="<%= repository.chomp('/') %>/blob/HEAD/<%= chapter.relative_path %>" target="_blank" rel="noopener">View on GitHub</a>
135
+ <a href="<%= h(repository.chomp('/')) %>/blob/HEAD/<%= h(chapter.relative_path) %>" target="_blank" rel="noopener">View on GitHub</a>
136
136
  </div>
137
137
  <%- end -%>
138
138
  <%- if footer && !chapter.part_title? && !chapter.cover? -%>
139
- <div class="chapter-footer"><%= footer %></div>
139
+ <div class="chapter-footer"><%= h(footer) %></div>
140
140
  <%- end -%>
141
141
  <%- unless chapter.cover? -%>
142
142
  <nav class="chapter-nav">
143
143
  <%- if idx > 0 -%>
144
- <a href="#" class="nav-prev" onclick="showChapter('<%= chapters[idx-1].slug %>'); return false;">&larr; <%= chapters[idx-1].display_title %></a>
144
+ <a href="#" class="nav-prev" onclick="showChapter('<%= chapters[idx-1].slug %>'); return false;">&larr; <%= h(chapters[idx-1].display_title) %></a>
145
145
  <%- else -%>
146
146
  <span></span>
147
147
  <%- end -%>
148
148
  <%- if idx < chapters.size - 1 -%>
149
- <a href="#" class="nav-next" onclick="showChapter('<%= chapters[idx+1].slug %>'); return false;"><%= chapters[idx+1].display_title %> &rarr;</a>
149
+ <a href="#" class="nav-next" onclick="showChapter('<%= chapters[idx+1].slug %>'); return false;"><%= h(chapters[idx+1].display_title) %> &rarr;</a>
150
150
  <%- end -%>
151
151
  </nav>
152
152
  <%- end -%>
@@ -157,7 +157,7 @@
157
157
  <h1><%= language == 'ja' ? '参考文献' : 'Bibliography' %></h1>
158
158
  <ul class="bibliography-list">
159
159
  <%- bibliography.each do |entry| -%>
160
- <li id="bib-<%= entry[:key] %>"><span class="bib-label">[<%= entry[:label] %>]</span> <%= entry[:formatted_html] %></li>
160
+ <li id="bib-<%= h(entry[:key]) %>"><span class="bib-label">[<%= h(entry[:label]) %>]</span> <%= entry[:formatted_html] %></li>
161
161
  <%- end -%>
162
162
  </ul>
163
163
  </section>
@@ -170,12 +170,12 @@
170
170
  <h2 class="index-letter"><%= letter %></h2>
171
171
  <dl class="index-entries">
172
172
  <%- index_tree[letter].each do |item| -%>
173
- <dt><%= item[:term] %></dt>
173
+ <dt><%= h(item[:term]) %></dt>
174
174
  <%- item[:refs].each do |ref| -%>
175
175
  <dd><a href="#<%= ref[:anchor_id] %>" onclick="showChapterAndScroll('<%= ref[:chapter_slug] %>', '<%= ref[:anchor_id] %>'); return false;"><%= ref[:chapter_title] %></a></dd>
176
176
  <%- end -%>
177
177
  <%- item[:children].each do |child| -%>
178
- <dt class="index-sub"><%= child[:term] %></dt>
178
+ <dt class="index-sub"><%= h(child[:term]) %></dt>
179
179
  <%- child[:refs].each do |ref| -%>
180
180
  <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
181
  <%- end -%>
@@ -248,16 +248,93 @@
248
248
  }
249
249
  });
250
250
  }
251
+ if (typeof functionPlot !== 'undefined') {
252
+ section.querySelectorAll('.functionplot[data-plot]').forEach(function(el) {
253
+ if (el.childNodes.length > 0) return;
254
+ try {
255
+ var spec = el.getAttribute('data-plot');
256
+ var opts = parseFunctionPlotSpec(spec);
257
+ opts.target = el;
258
+ functionPlot(opts);
259
+ } catch(e) {
260
+ el.textContent = 'Error rendering plot: ' + e.message;
261
+ el.style.color = 'red';
262
+ }
263
+ });
264
+ }
265
+ }
266
+
267
+ // Parse functionplot spec: lines of "y = expr" and "key: value" options
268
+ function parseFunctionPlotSpec(spec) {
269
+ var lines = spec.split('\n');
270
+ var fns = [];
271
+ var opts = {width: 600, height: 400};
272
+ lines.forEach(function(line) {
273
+ line = line.trim();
274
+ if (!line) return;
275
+ // Option lines: key: value
276
+ var optMatch = line.match(/^(xrange|yrange|range|width|height|title|grid)\s*:\s*(.+)$/i);
277
+ if (optMatch) {
278
+ var key = optMatch[1].toLowerCase();
279
+ var val = optMatch[2].trim();
280
+ if (key === 'range' || key === 'xrange') {
281
+ opts.xAxis = {domain: parseRange(val)};
282
+ } else if (key === 'yrange') {
283
+ opts.yAxis = {domain: parseRange(val)};
284
+ } else if (key === 'width') {
285
+ opts.width = parseInt(val, 10);
286
+ } else if (key === 'height') {
287
+ opts.height = parseInt(val, 10);
288
+ } else if (key === 'title') {
289
+ opts.title = val;
290
+ } else if (key === 'grid') {
291
+ opts.grid = val === 'true';
292
+ }
293
+ return;
294
+ }
295
+ // Function lines: y = expr, r = expr (polar), x = expr (implicit), or bare expr
296
+ var fnMatch = line.match(/^y\s*=\s*(.+)$/);
297
+ if (fnMatch) { fns.push({fn: fnMatch[1].trim()}); return; }
298
+ var polarMatch = line.match(/^r\s*=\s*(.+)$/);
299
+ if (polarMatch) { fns.push({r: polarMatch[1].trim(), fnType: 'polar', graphType: 'polyline'}); return; }
300
+ var paramMatch = line.match(/^parametric\s*:\s*(.+?)\s*,\s*(.+)$/);
301
+ if (paramMatch) { fns.push({x: paramMatch[1].trim(), y: paramMatch[2].trim(), fnType: 'parametric', graphType: 'polyline'}); return; }
302
+ // Bare expression treated as y = expr
303
+ fns.push({fn: line});
304
+ });
305
+ opts.data = fns;
306
+ return opts;
307
+ }
308
+
309
+ function parseRange(str) {
310
+ var m = str.match(/\[?\s*(-?[\d.eE+piPI*\/]+)\s*,\s*(-?[\d.eE+piPI*\/]+)\s*\]?/);
311
+ if (!m) return [-10, 10];
312
+ return [evalRangeNum(m[1]), evalRangeNum(m[2])];
313
+ }
314
+
315
+ function evalRangeNum(s) {
316
+ s = s.replace(/pi/gi, String(Math.PI));
317
+ try { return Function('"use strict"; return (' + s + ')')(); }
318
+ catch(e) { return parseFloat(s) || 0; }
251
319
  }
252
320
 
253
321
  function showChapterAndScroll(slug, headingId) {
254
322
  showChapter(slug, headingId);
255
323
  setTimeout(function() {
256
324
  var target = document.getElementById(headingId);
257
- if (target) target.scrollIntoView({ behavior: 'smooth' });
325
+ if (target) {
326
+ target.scrollIntoView({ behavior: 'smooth' });
327
+ highlightElement(target);
328
+ }
258
329
  }, 50);
259
330
  }
260
331
 
332
+ function highlightElement(el) {
333
+ var prev = document.querySelector('.ligarb-highlight');
334
+ if (prev) prev.classList.remove('ligarb-highlight');
335
+ el.classList.add('ligarb-highlight');
336
+ }
337
+
261
338
  // Navigate to chapter/heading based on current URL hash
262
339
  function handleHash() {
263
340
  navigating = true;
@@ -542,5 +619,10 @@ renderSpecialBlocks();
542
619
  <script src="js/katex.min.js"></script>
543
620
  <script>renderSpecialBlocks();</script>
544
621
  <%- end -%>
622
+ <%- if assets.need?(:functionplot) -%>
623
+ <script src="js/d3.min.js"></script>
624
+ <script src="js/function-plot.min.js"></script>
625
+ <script>renderSpecialBlocks();</script>
626
+ <%- end -%>
545
627
  </body>
546
628
  </html>
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ligarb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ligarb contributors