Repository navigation
Expand file tree
/
Copy pathindex.html
More file actions
674 lines (615 loc) · 32.6 KB
/
Copy pathindex.html
File metadata and controls
674 lines (615 loc) · 32.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
---
layout: default
title: "Home"
description: "人形机器人学习方向的每日论文精读笔记:强化学习、动作重定向、全身控制、Sim-to-Real 等方向的中文笔记,附学习路线图、交互演示与讲解视频。"
---
<header class="index-header">
<h1 data-en="{{ site.title | escape }}" data-zh="{{ site.description | escape }}">{{ site.title | escape }}</h1>
<p class="index-lede" data-en="Close readings of humanoid robot learning papers, written in Chinese: reinforcement learning, motion retargeting, whole body control and sim to real transfer, with a learning roadmap, interactive demos and narrated videos." data-zh="人形机器人学习方向的论文精读笔记:强化学习、动作重定向、全身控制、仿真到现实迁移,附学习路线图、交互演示与配音讲解视频。">Close readings of humanoid robot learning papers, written in Chinese: reinforcement learning, motion retargeting, whole body control and sim to real transfer, with a learning roadmap, interactive demos and narrated videos.</p>
{% assign paper_count = 0 %}
{% for cat in site.data.papers %}
{% assign cat_papers_size = cat[1].papers | size %}
{% assign paper_count = paper_count | plus: cat_papers_size %}
{% if cat[1].subcategories %}
{% for subcat in cat[1].subcategories %}
{% assign subcat_papers_size = subcat.papers | size %}
{% assign paper_count = paper_count | plus: subcat_papers_size %}
{% endfor %}
{% endif %}
{% endfor %}
<p class="index-meta-chips">
<span class="index-chip" id="paper-count-label">
<svg class="icon" aria-hidden="true" focusable="false"><use href="#i-file-text"></use></svg>
<span data-en="{{ paper_count | escape }} notes" data-zh="共 {{ paper_count | escape }} 篇笔记">{{ paper_count | escape }} notes</span>
</span>
<a href="{{ '/updates.html' | relative_url }}" class="index-chip index-chip-link">
<svg class="icon" aria-hidden="true" focusable="false"><use href="#i-clock-counter-clockwise"></use></svg>
<span data-en="Update log" data-zh="更新记录">Update log</span>
<span class="index-chip-arrow" aria-hidden="true"><svg class="icon" focusable="false"><use href="#i-arrow-right"></use></svg></span>
</a>
</p>
</header>
<div class="index-layout">
<aside class="toc-sidebar" id="toc-sidebar">
<div class="toc-wrapper">
<h3 class="toc-title" data-en="Categories" data-zh="分类导航">分类导航</h3>
<nav class="toc-nav">
<ul class="toc-list">
<li class="toc-item toc-h2" data-toc-for="roadmap-title">
<a href="#roadmap-title" class="toc-link toc-link-roadmap">
<svg class="icon" aria-hidden="true" focusable="false"><use href="#i-map-pin"></use></svg><span data-en="Learning roadmap" data-zh="学习路线图">Learning roadmap</span>
</a>
</li>
{% for cat in site.data.papers %}
{% assign cat_papers_size = cat[1].papers | size %}
{% assign total_count = cat_papers_size %}
{% if cat[1].subcategories %}
{% for subcat in cat[1].subcategories %}
{% assign subcat_papers_size = subcat.papers | size %}
{% assign total_count = total_count | plus: subcat_papers_size %}
{% endfor %}
{% endif %}
<li class="toc-item toc-h2" data-toc-for="cat-{{ forloop.index }}">
<a href="#cat-{{ forloop.index }}" class="toc-link"><span data-en="{{ cat[1].display_name | escape }}" data-zh="{{ cat[1].zhname | escape }}">{{ cat[1].display_name | escape }}</span> <span class="toc-count">({{ total_count }})</span></a>
{% if cat[1].subcategories %}
<ul class="toc-sublist">
{% for subcat in cat[1].subcategories %}
{% assign subcat_size = subcat.papers | size %}
<li class="toc-item toc-h3" data-toc-for="subcat-{{ forloop.parentloop.index }}-{{ forloop.index }}">
<a href="#subcat-{{ forloop.parentloop.index }}-{{ forloop.index }}" class="toc-link toc-link-sub"><span data-en="{{ subcat.name | escape }}" data-zh="{{ subcat.zhname | escape }}">{{ subcat.name | escape }}</span> <span class="toc-count">({{ subcat_size }})</span></a>
</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
</nav>
</div>
</aside>
<div class="index-main" id="index-main">
<p class="index-legend"><svg class="icon" aria-hidden="true" focusable="false"><use href="#i-star"></use></svg><span data-en="Note links to open source code" data-zh="笔记含开源源码链接">Note links to open source code</span></p>
<div class="search-container" role="search">
<svg class="icon search-icon" aria-hidden="true" focusable="false"><use href="#i-magnifying-glass"></use></svg>
<input type="text" id="paper-search" placeholder="Search by title or arXiv ID..." data-en-placeholder="Search by title or arXiv ID..." data-zh-placeholder="通过论文标题或 arXiv 编号搜索..." aria-label="Search papers" data-en-aria-label="Search papers" data-zh-aria-label="搜索论文">
</div>
<div class="mermaid-container" id="roadmap-title">
<details>
<summary class="mermaid-title roadmap-entry">
<span class="roadmap-entry-copy">
<span class="roadmap-entry-label"><svg class="icon" aria-hidden="true" focusable="false"><use href="#i-map-pin"></use></svg><span data-en="Recommended learning roadmap" data-zh="推荐学习路线图">推荐学习路线图</span></span>
<span class="roadmap-entry-hint" data-en="Not sure where to start? Follow the path, one paper at a time." data-zh="不知道先读哪篇?沿着路线,一步步读懂机器人学习。">不知道先读哪篇?沿着路线,一步步读懂机器人学习。</span>
</span>
<span class="roadmap-entry-cta">
<span class="roadmap-entry-start" data-en="Start here" data-zh="从这里开始">从这里开始</span>
<span class="roadmap-entry-close" data-en="Collapse" data-zh="收起路线图">收起路线图</span>
<span class="roadmap-entry-arrow" aria-hidden="true"><svg class="icon" focusable="false"><use href="#i-arrow-right"></use></svg></span>
</span>
</summary>
<p class="roadmap-link-hint" data-en="Hover over a node for its details; click it to open the note. Nodes marked “paper” open the original paper. 🎬 marks notes with a narrated video. Click elsewhere on the diagram to zoom in." data-zh="鼠标悬停节点查看详情,点击阅读笔记;标注“原文”的节点打开论文。🎬 表示笔记含配音讲解视频。点击图中其他区域可放大查看。">Hover over a node for its details; click it to open the note. Nodes marked “paper” open the original paper. 🎬 marks notes with a narrated video. Click elsewhere on the diagram to zoom in.</p>
<div class="mermaid" id="roadmap-mermaid"></div>
</details>
</div>
<script src="{{ '/assets/js/roadmap-entry.js' | relative_url }}" defer></script>
<script type="application/json" id="roadmap-node-links">{
{%- assign link_first = true -%}
{%- for pair in site.data.roadmap_links -%}
{%- unless link_first -%},{%- endunless -%}
{%- assign link_first = false -%}
{%- assign link_url = pair[1] -%}
{%- assign link_prefix = link_url | slice: 0, 8 -%}
{%- unless link_prefix == 'https://' -%}
{%- assign link_url = link_url | relative_url -%}
{%- endunless -%}
{{ pair[0] | jsonify | replace: '<', '\u003c' | replace: '>', '\u003e' | replace: '&', '\u0026' }}: {{ link_url | jsonify | replace: '<', '\u003c' | replace: '>', '\u003e' | replace: '&', '\u0026' }}
{%- endfor -%}
}</script>
{%- comment -%}
路线图节点悬浮卡片的数据:按 roadmap_links 的笔记地址在 _data/papers.json 里找到那篇论文,
只输出卡片要用的字段(标题、板块、发表时间、arXiv、开源、简介、讲解动画幕数、配音视频时长)。
{%- endcomment %}
<script type="application/json" id="roadmap-node-details">{
{%- assign detail_first = true -%}
{%- for pair in site.data.roadmap_links -%}
{%- assign detail_paper = nil -%}
{%- assign detail_cat = nil -%}
{%- for cat in site.data.papers -%}
{%- assign detail_hit = cat[1].papers | where: "url", pair[1] | first -%}
{%- unless detail_hit -%}
{%- for sc in cat[1].subcategories -%}
{%- assign detail_hit = sc.papers | where: "url", pair[1] | first -%}
{%- if detail_hit -%}{%- break -%}{%- endif -%}
{%- endfor -%}
{%- endunless -%}
{%- if detail_hit -%}
{%- assign detail_paper = detail_hit -%}
{%- assign detail_cat = cat[1] -%}
{%- break -%}
{%- endif -%}
{%- endfor -%}
{%- if detail_paper -%}
{%- unless detail_first -%},{%- endunless -%}
{%- assign detail_first = false -%}
{{ pair[0] | jsonify | replace: '<', '\u003c' | replace: '>', '\u003e' | replace: '&', '\u0026' }}: {
"title": {{ detail_paper.title | jsonify | replace: '<', '\u003c' | replace: '>', '\u003e' | replace: '&', '\u0026' }},
"zh_title": {{ detail_paper.zh_title | default: detail_paper.zhname | jsonify | replace: '<', '\u003c' | replace: '>', '\u003e' | replace: '&', '\u0026' }},
"category": {{ detail_cat.display_name | jsonify | replace: '<', '\u003c' | replace: '>', '\u003e' | replace: '&', '\u0026' }},
"category_zh": {{ detail_cat.zhname | default: detail_cat.display_name | jsonify | replace: '<', '\u003c' | replace: '>', '\u003e' | replace: '&', '\u0026' }},
"date": {{ detail_paper.published_date_en | jsonify | replace: '<', '\u003c' | replace: '>', '\u003e' | replace: '&', '\u0026' }},
"date_zh": {{ detail_paper.published_date_zh | jsonify | replace: '<', '\u003c' | replace: '>', '\u003e' | replace: '&', '\u0026' }},
"arxiv": {{ detail_paper.arxiv | jsonify | replace: '<', '\u003c' | replace: '>', '\u003e' | replace: '&', '\u0026' }},
"open_source": {{ detail_paper.has_open_source | default: false | jsonify | replace: '<', '\u003c' | replace: '>', '\u003e' | replace: '&', '\u0026' }},
"summary_zh": {{ detail_paper.summary_zh | jsonify | replace: '<', '\u003c' | replace: '>', '\u003e' | replace: '&', '\u0026' }},
"scenes": {{ detail_paper.explainer_scenes | jsonify | replace: '<', '\u003c' | replace: '>', '\u003e' | replace: '&', '\u0026' }},
"video_seconds": {{ detail_paper.video_seconds | jsonify | replace: '<', '\u003c' | replace: '>', '\u003e' | replace: '&', '\u0026' }}
}
{%- endif -%}
{%- endfor -%}
}</script>
<div id="search-no-results" class="search-no-results" role="status" data-en="No notes match that search. Try an abbreviation such as PPO, or an arXiv ID such as 1707.06347." data-zh="没有找到匹配的笔记。试试英文缩写(如 PPO)或 arXiv 编号(如 1707.06347)。">No notes match that search. Try an abbreviation such as PPO, or an arXiv ID such as 1707.06347.</div>
{% comment %} First try _data/papers.json (generated by prepare script) {% endcomment %}
{% if site.data.papers %}
{% assign fold_limit = 5 %}
{% assign fold_exempt_keys = "01_Foundational_RL,02_Motion_Retargeting,03_High_Impact_Selection" | split: "," %}
{% for cat in site.data.papers %}
{% assign cat_key = cat[0] %}
{% assign cat_data = cat[1] %}
<div class="category-section" data-category="{{ cat_data.display_name | escape }}">
{% assign cat_papers_size = cat_data.papers | size %}
{% assign main_total = cat_papers_size %}
{% if cat_data.subcategories %}
{% for subcat in cat_data.subcategories %}
{% assign subcat_papers_size = subcat.papers | size %}
{% assign main_total = main_total | plus: subcat_papers_size %}
{% endfor %}
{% endif %}
<h2 class="category-title" id="cat-{{ forloop.index }}"><span class="cat-index" aria-hidden="true">{{ cat_key | slice: 0, 2 }}</span> <span class="cat-name" data-en="{{ cat_data.display_name | escape }}" data-zh="{{ cat_data.zhname | escape }}">{{ cat_data.display_name | escape }}</span> <span class="paper-count">({{ main_total }})</span></h2>
{% if cat_data.subtitle %}<p class="category-subtitle" data-en="{{ cat_data.subtitle | escape }}" data-zh="{{ cat_data.subtitle_zh | escape }}">{{ cat_data.subtitle | escape }}</p>{% endif %}
{% if cat_data.sort_order_hint %}<p class="category-sort-hint" data-en="{{ cat_data.sort_order_hint | escape }}" data-zh="{{ cat_data.sort_order_hint_zh | escape }}">{{ cat_data.sort_order_hint | escape }}</p>{% endif %}
{% if cat_data.subcategories %}
{% if cat_data.papers.size > 0 %}
<ul class="paper-list">
{% for paper in cat_data.papers %}
<li data-search="{{ paper.title | escape }} {{ paper.zhname | escape }} {{ paper.arxiv | escape }}">
{% include paper-card.html paper=paper %}
</li>
{% endfor %}
</ul>
{% endif %}
{% for subcat in cat_data.subcategories %}
<div class="subcategory-section" data-subcategory="{{ subcat.name | escape }}">
<h3 class="subcategory-title" id="subcat-{{ forloop.parentloop.index }}-{{ forloop.index }}">
<span class="subcat-name" data-en="{{ subcat.name | escape }}" data-zh="{{ subcat.zhname | escape }}">{{ subcat.name | escape }}</span>
<span class="paper-count">({{ subcat.papers | size }})</span>
</h3>
{% if subcat.sort_order_hint %}<p class="subcategory-sort-hint" data-en="{{ subcat.sort_order_hint | escape }}" data-zh="{{ subcat.sort_order_hint_zh | escape }}">{{ subcat.sort_order_hint | escape }}</p>{% endif %}
{% if subcat.papers.size > 0 %}
<ul class="paper-list">
{% for paper in subcat.papers %}
<li data-search="{{ paper.title | escape }} {{ paper.zhname | escape }} {{ paper.arxiv | escape }}">
{% include paper-card.html paper=paper %}
</li>
{% endfor %}
</ul>
{% else %}
<p class="empty-hint" data-en="No notes in this section yet." data-zh="这个分类还没有笔记。">No notes in this section yet.</p>
{% endif %}
</div>
{% endfor %}
{% elsif cat_data.papers.size > 0 %}
{% assign fold_exempt = false %}
{% for exempt_key in fold_exempt_keys %}
{% if cat_key == exempt_key %}
{% assign fold_exempt = true %}
{% endif %}
{% endfor %}
{% assign should_fold = false %}
{% unless fold_exempt %}
{% if cat_data.papers.size > fold_limit %}
{% assign should_fold = true %}
{% assign fold_hidden = cat_data.papers.size | minus: fold_limit %}
{% endif %}
{% endunless %}
<ul class="paper-list{% if should_fold %} has-paper-fold is-folded{% endif %}"{% if should_fold %} data-fold-id="cat-{{ forloop.index }}-papers" data-fold-step="{{ fold_limit }}" data-fold-initial="{{ fold_limit }}"{% endif %}>
{% for paper in cat_data.papers %}
<li{% if should_fold and forloop.index > fold_limit %} class="paper-list-item-folded"{% endif %} data-search="{{ paper.title | escape }} {{ paper.zhname | escape }} {{ paper.arxiv | escape }}">
{% include paper-card.html paper=paper %}
</li>
{% endfor %}
</ul>
{% if should_fold %}
<div class="paper-list-toggle-group" data-fold-id="cat-{{ forloop.index }}-papers">
<button type="button" class="paper-list-toggle-btn paper-list-more-btn" data-fold-id="cat-{{ forloop.index }}-papers"
data-en="Show {{ fold_limit }} more ▾"
data-zh="再展开 {{ fold_limit }} 篇 ▾">Show {{ fold_limit }} more ▾</button>
<button type="button" class="paper-list-toggle-btn paper-list-all-btn" data-fold-id="cat-{{ forloop.index }}-papers"
data-en="Show all {{ fold_hidden }} notes ▾"
data-zh="展开全部 {{ fold_hidden }} 篇 ▾">Show all {{ fold_hidden }} notes ▾</button>
<button type="button" class="paper-list-toggle-btn paper-list-collapse-btn" data-fold-id="cat-{{ forloop.index }}-papers"
data-en="Collapse to {{ fold_limit }} ▴"
data-zh="收起到 {{ fold_limit }} 篇 ▴"
hidden>Collapse to {{ fold_limit }} ▴</button>
</div>
{% endif %}
{% else %}
<p class="empty-hint" data-en="No notes in this section yet." data-zh="这个分类还没有笔记。">No notes in this section yet.</p>
{% endif %}
</div>
{% endfor %}
{% else %}
{% comment %} Fallback: iterate site.pages directly {% endcomment %}
{% assign paper_pages = site.pages | where_exp: "p", "p.url contains '/papers/'" | sort: "url" %}
{% assign current_cat = "" %}
{% for page in paper_pages %}
{% assign parts = page.url | split: "/" %}
{% if parts.size >= 4 %}
{% assign cat = parts[2] | replace: "_", " " | remove_first: "01 " | remove_first: "02 " | remove_first: "03 " | remove_first: "04 " | remove_first: "05 " | remove_first: "06 " | remove_first: "07 " | remove_first: "08 " | remove_first: "09 " | remove_first: "10 " | remove_first: "11 " | remove_first: "12 " | remove_first: "13 " %}
{% if cat != current_cat %}
{% unless current_cat == "" %}</ul></div>{% endunless %}
<div class="category-section">
<h2 class="category-title">{{ cat | escape }}</h2>
<ul class="paper-list">
{% assign current_cat = cat %}
{% endif %}
<li data-search="{{ page.title | escape }}">
<a href="{{ page.url | relative_url | escape }}" class="paper-card">
<span class="paper-title">{% if page.title %}{{ page.title | escape }}{% else %}{{ parts[3] | replace: "_", " " | escape }}{% endif %}</span>
</a>
</li>
{% endif %}
{% endfor %}
{% unless current_cat == "" %}</ul></div>{% endunless %}
{% endif %}
</div>
</div>
<script>
(function() {
var searchInput = document.getElementById('paper-search');
var noResults = document.getElementById('search-no-results');
var categories = document.querySelectorAll('.category-section');
var paperItems = document.querySelectorAll('.paper-list li[data-search]');
// ⚡ Bolt Optimization: Cache DOM structure and search text once on page load to prevent expensive `querySelectorAll` and `getAttribute` during typing
var cachedCategories = Array.from(categories).map(function(cat) {
var catTitle = cat.querySelector('.category-title');
var catTocItem = catTitle ? document.querySelector('.toc-item[data-toc-for="' + catTitle.id + '"]') : null;
var subcategories = Array.from(cat.querySelectorAll('.subcategory-section')).map(function(subcat) {
var subcatTitle = subcat.querySelector('.subcategory-title');
var subcatTocItem = subcatTitle ? document.querySelector('.toc-item[data-toc-for="' + subcatTitle.id + '"]') : null;
var items = Array.from(subcat.querySelectorAll('li[data-search]')).map(function(item) {
return { el: item, text: item.getAttribute('data-search').toLowerCase() };
});
return { el: subcat, tocItem: subcatTocItem, items: items };
});
// Handle polyfill for :scope if needed, but for our case, children match is safer.
// Instead of using :scope > .paper-list > li[data-search], we select .paper-list > li[data-search]
// that are direct children of the category-section (not inside subcategories)
var directItems = [];
var directLists = cat.children;
for (var i = 0; i < directLists.length; i++) {
if (directLists[i].classList.contains('paper-list')) {
var items = directLists[i].querySelectorAll('li[data-search]');
for (var j = 0; j < items.length; j++) {
directItems.push({ el: items[j], text: items[j].getAttribute('data-search').toLowerCase() });
}
}
}
return {
el: cat,
tocItem: catTocItem,
subcategories: subcategories,
directItems: directItems
};
});
var roadmapTocItem = document.querySelector('.toc-item[data-toc-for="roadmap-title"]');
var searchTimeout;
var FOLD_PREFIX = 'paper-fold:';
var foldLists = Array.from(document.querySelectorAll('.paper-list.has-paper-fold[data-fold-id]'));
var foldStateBeforeSearch = null;
function getLang() {
return document.documentElement.getAttribute('data-lang-mode') === 'zh' ? 'zh' : 'en';
}
function getFoldedItems(list) {
return Array.from(list.querySelectorAll('li.paper-list-item-folded'));
}
function getVisibleCount(list) {
return list.querySelectorAll('li[data-search]').length - getFoldedItems(list).length;
}
function getFoldGroup(foldId) {
return document.querySelector('.paper-list-toggle-group[data-fold-id="' + foldId + '"]');
}
function updateFoldLabels(group, hiddenCount, step, initial) {
var moreBtn = group.querySelector('.paper-list-more-btn');
var allBtn = group.querySelector('.paper-list-all-btn');
var collapseBtn = group.querySelector('.paper-list-collapse-btn');
var useZh = getLang() === 'zh';
var moreCount = Math.min(step, hiddenCount);
if (moreBtn) {
moreBtn.textContent = useZh
? '再展开 ' + moreCount + ' 篇 ▾'
: 'Show ' + moreCount + ' more ▾';
}
if (allBtn) {
allBtn.textContent = useZh
? '展开全部 ' + hiddenCount + ' 篇 ▾'
: 'Show all ' + hiddenCount + ' notes ▾';
}
if (collapseBtn) {
collapseBtn.textContent = useZh
? '收起到 ' + initial + ' 篇 ▴'
: 'Collapse to ' + initial + ' ▴';
}
}
function updateFoldUI(list, persist) {
var foldId = list.getAttribute('data-fold-id');
var group = getFoldGroup(foldId);
var hiddenCount = getFoldedItems(list).length;
var step = parseInt(list.getAttribute('data-fold-step') || '5', 10);
var initial = parseInt(list.getAttribute('data-fold-initial') || '5', 10);
var visibleCount = getVisibleCount(list);
var expandedBeyondDefault = visibleCount > initial;
if (hiddenCount === 0) {
list.classList.remove('is-folded');
} else {
list.classList.add('is-folded');
}
if (!group) return;
var moreBtn = group.querySelector('.paper-list-more-btn');
var allBtn = group.querySelector('.paper-list-all-btn');
var collapseBtn = group.querySelector('.paper-list-collapse-btn');
group.style.display = '';
if (moreBtn) moreBtn.hidden = hiddenCount === 0;
if (allBtn) allBtn.hidden = hiddenCount === 0;
if (collapseBtn) collapseBtn.hidden = !expandedBeyondDefault;
updateFoldLabels(group, hiddenCount, step, initial);
if (persist && foldId) {
try {
localStorage.setItem(FOLD_PREFIX + foldId, String(visibleCount));
} catch (e) {}
}
}
function applyVisibleCount(list, targetVisible, persist) {
var items = Array.from(list.querySelectorAll('li[data-search]'));
var initial = parseInt(list.getAttribute('data-fold-initial') || '5', 10);
var target = Math.max(initial, Math.min(items.length, targetVisible));
items.forEach(function(li, idx) {
if (idx < target) li.classList.remove('paper-list-item-folded');
else li.classList.add('paper-list-item-folded');
});
updateFoldUI(list, persist);
}
foldLists.forEach(function(list) {
var foldId = list.getAttribute('data-fold-id');
var group = getFoldGroup(foldId);
if (!group) return;
var saved = null;
try {
saved = localStorage.getItem(FOLD_PREFIX + foldId);
} catch (e) {}
if (saved === 'true') {
applyVisibleCount(list, list.querySelectorAll('li[data-search]').length, false);
} else if (saved && saved !== 'false') {
var target = parseInt(saved, 10);
if (!isNaN(target)) applyVisibleCount(list, target, false);
else updateFoldUI(list, false);
} else {
updateFoldUI(list, false);
}
var moreBtn = group.querySelector('.paper-list-more-btn');
var allBtn = group.querySelector('.paper-list-all-btn');
var collapseBtn = group.querySelector('.paper-list-collapse-btn');
if (moreBtn) {
moreBtn.addEventListener('click', function() {
var step = parseInt(list.getAttribute('data-fold-step') || '5', 10);
applyVisibleCount(list, getVisibleCount(list) + step, true);
});
}
if (allBtn) {
allBtn.addEventListener('click', function() {
applyVisibleCount(list, list.querySelectorAll('li[data-search]').length, true);
});
}
if (collapseBtn) {
collapseBtn.addEventListener('click', function() {
var initial = parseInt(list.getAttribute('data-fold-initial') || '5', 10);
applyVisibleCount(list, initial, true);
});
}
});
document.addEventListener('cl-lang-changed', function() {
foldLists.forEach(function(list) {
var foldId = list.getAttribute('data-fold-id');
var group = getFoldGroup(foldId);
if (!group) return;
var step = parseInt(list.getAttribute('data-fold-step') || '5', 10);
var initial = parseInt(list.getAttribute('data-fold-initial') || '5', 10);
updateFoldLabels(group, getFoldedItems(list).length, step, initial);
});
});
function setSearchFoldMode(active) {
if (active) {
if (foldStateBeforeSearch) return;
foldStateBeforeSearch = new Map();
foldLists.forEach(function(list) {
var id = list.getAttribute('data-fold-id');
foldStateBeforeSearch.set(id, getVisibleCount(list));
list.classList.remove('is-folded');
});
document.body.classList.add('index-search-active');
return;
}
if (!foldStateBeforeSearch) return;
foldLists.forEach(function(list) {
var id = list.getAttribute('data-fold-id');
applyVisibleCount(list, foldStateBeforeSearch.get(id), false);
});
foldStateBeforeSearch = null;
document.body.classList.remove('index-search-active');
}
function resetAllItemDisplays() {
cachedCategories.forEach(function(catCache) {
catCache.subcategories.forEach(function(subcatCache) {
subcatCache.items.forEach(function(itemCache) {
if (itemCache.el.style.display === 'none') itemCache.el.style.removeProperty('display');
});
if (subcatCache.el.style.display === 'none') subcatCache.el.style.removeProperty('display');
});
catCache.directItems.forEach(function(itemCache) {
if (itemCache.el.style.display === 'none') itemCache.el.style.removeProperty('display');
});
if (catCache.el.style.display === 'none') catCache.el.style.removeProperty('display');
});
}
function runSearch() {
if (!searchInput) return;
var query = searchInput.value.toLowerCase().trim();
var hasAnyMatch = false;
var isSearching = query !== '';
setSearchFoldMode(isSearching);
if (!isSearching) {
resetAllItemDisplays();
}
if (roadmapTocItem) {
var targetRoadmapDisplay = query === '' ? '' : 'none';
if (roadmapTocItem.style.display !== targetRoadmapDisplay) roadmapTocItem.style.display = targetRoadmapDisplay;
}
cachedCategories.forEach(function(catCache) {
var catHasMatch = false;
var catTotalMatch = 0;
catCache.subcategories.forEach(function(subcatCache) {
var subcatHasMatch = false;
var subcatMatchCount = 0;
subcatCache.items.forEach(function(itemCache) {
if (!isSearching || itemCache.text.indexOf(query) !== -1) {
// ⚡ Bolt Optimization: Strict diff-check to prevent layout thrashing
if (itemCache.el.style.display === 'none') itemCache.el.style.removeProperty('display');
if (isSearching) {
subcatHasMatch = true;
subcatMatchCount++;
catHasMatch = true;
hasAnyMatch = true;
}
} else {
// ⚡ Bolt Optimization: Strict diff-check to prevent layout thrashing
if (itemCache.el.style.display !== 'none') itemCache.el.style.display = 'none';
}
});
var targetSubcatDisplay = subcatHasMatch || query === '' ? '' : 'none';
if (subcatCache.el.style.display !== targetSubcatDisplay) subcatCache.el.style.display = targetSubcatDisplay;
if (subcatCache.tocItem) {
if (subcatCache.tocItem.style.display !== targetSubcatDisplay) subcatCache.tocItem.style.display = targetSubcatDisplay;
var countEl = subcatCache.tocItem.querySelector('.toc-count');
if (countEl) {
if (countEl.dataset.origCount === undefined) countEl.dataset.origCount = countEl.textContent;
var targetText = isSearching ? '(' + subcatMatchCount + ')' : countEl.dataset.origCount;
if (countEl.textContent !== targetText) countEl.textContent = targetText;
}
}
if (isSearching) catTotalMatch += subcatMatchCount;
});
var directMatchCount = 0;
catCache.directItems.forEach(function(itemCache) {
if (!isSearching || itemCache.text.indexOf(query) !== -1) {
// ⚡ Bolt Optimization: Strict diff-check to prevent layout thrashing
if (itemCache.el.style.display === 'none') itemCache.el.style.removeProperty('display');
if (isSearching) {
catHasMatch = true;
hasAnyMatch = true;
directMatchCount++;
}
} else {
// ⚡ Bolt Optimization: Strict diff-check to prevent layout thrashing
if (itemCache.el.style.display !== 'none') itemCache.el.style.display = 'none';
}
});
if (isSearching) catTotalMatch += directMatchCount;
var targetCatDisplay = catHasMatch || query === '' ? '' : 'none';
if (catCache.el.style.display !== targetCatDisplay) catCache.el.style.display = targetCatDisplay;
if (catCache.tocItem) {
if (catCache.tocItem.style.display !== targetCatDisplay) catCache.tocItem.style.display = targetCatDisplay;
var countEl = catCache.tocItem.querySelector('.toc-count');
if (countEl) {
if (countEl.dataset.origCount === undefined) countEl.dataset.origCount = countEl.textContent;
var targetText = isSearching ? '(' + catTotalMatch + ')' : countEl.dataset.origCount;
if (countEl.textContent !== targetText) countEl.textContent = targetText;
}
}
});
if (noResults) {
var targetNoResultsDisplay = hasAnyMatch || query === '' ? 'none' : 'block';
if (noResults.style.display !== targetNoResultsDisplay) noResults.style.display = targetNoResultsDisplay;
}
}
if (searchInput) {
searchInput.addEventListener('input', function(e) {
if (e.isComposing) return;
clearTimeout(searchTimeout);
searchTimeout = setTimeout(runSearch, 150);
});
searchInput.addEventListener('compositionend', function() {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(runSearch, 0);
});
}
var sidebar = document.getElementById('toc-sidebar');
if (!sidebar) return;
var links = sidebar.querySelectorAll('.toc-link');
var targets = [];
links.forEach(function(a) {
var id = a.getAttribute('href').slice(1);
var el = document.getElementById(id);
if (el) targets.push({ link: a, el: el });
});
if (targets.length === 0) return;
var lastActive = -1;
// ⚡ Bolt Optimization: Cache layout positions to prevent layout thrashing on scroll
var cachedPositions = [];
function updatePositions() {
cachedPositions = targets.map(function(t) {
return t.el.getBoundingClientRect().top + window.scrollY;
});
}
updatePositions();
if (window.ResizeObserver) {
var ro = new ResizeObserver(updatePositions);
ro.observe(document.body);
} else {
window.addEventListener('resize', function() {
clearTimeout(window.resizeTimeout);
window.resizeTimeout = setTimeout(updatePositions, 150);
});
}
function updateActive() {
// Probe just below the sticky header so the link reflects whatever
// section is currently anchored at the top of the viewport.
var probeY = 140;
var current = -1;
var scrollThreshold = window.scrollY + probeY;
for (var i = 0; i < cachedPositions.length; i++) {
if (cachedPositions[i] <= scrollThreshold) current = i;
}
// Only update DOM if the active section actually changed
if (current !== lastActive) {
// ⚡ Bolt Optimization: O(1) targeted DOM class toggling instead of O(N) loop
if (lastActive >= 0 && targets[lastActive]) {
targets[lastActive].link.classList.remove('active');
}
if (current >= 0 && targets[current]) {
targets[current].link.classList.add('active');
}
lastActive = current;
}
}
var ticking = false;
// ⚡ Bolt Optimization: Throttle scroll event listener using requestAnimationFrame to prevent multiple calls per frame
window.addEventListener('scroll', function() {
if (!ticking) {
window.requestAnimationFrame(function() {
updateActive();
ticking = false;
});
ticking = true;
}
});
updateActive();
})();
</script>