al_folio_core 1.0.3 → 1.0.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a15b5c9e5bcd706d7d8fe4905dc6808ff410a61e75ab5e9f56005d55701a09eb
4
- data.tar.gz: d85d3073a73190a3de3d848f344a963de62a6ddc9820e8d5ef3a7d59a5892721
3
+ metadata.gz: 566d64497199ae021c6b80241c4bf25791fb6447a7e22ee0232fb74e2cd915bb
4
+ data.tar.gz: 149241d6a27b9e51737e6b437c339c270f64abc379e0c2664976c200af94fb37
5
5
  SHA512:
6
- metadata.gz: 682d3c7b5226138717d8519612f8a619a72935dd901ec957375e0497edb1f8558e7ea54bb048d781d8c706e55b58241416488d2f22fa66943d9469db38a8f864
7
- data.tar.gz: 8112481419f442d856e68349c4e58279fc53c37dd9a03460280b86a7389ce67c8ff2be1a40ff9364866030fe7f4987fb5976f60ce732c0527909e447f1f07dcc
6
+ metadata.gz: 2eba8f9aeebd03783e40e1833ceba8990bae51bb068065603c8f9bba6d24c8286c11f14d5366021a7295cbb3e8dd5a565bbc65a752868e8dbe758d1f34a284b1
7
+ data.tar.gz: 6dccbdc6e0ab582dcf00a5fe0dc64ecb8418bc97f9d6051dbb214ca6a1a28f217429ca8c5d51ec0cbf472007ba4c2da1041da61d212e10c10f70bedc2723a4b4
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.5 - 2026-02-18
4
+
5
+ - Restored right-aligned desktop navbar menu layout with explicit core-owned alignment classes.
6
+ - Matched inline code typography more closely to legacy sizing/weight while preserving code-block styling.
7
+ - Normalized related-post recommendation links to regular font weight.
8
+ - Fixed Tocbot sidebar visual clashes by removing competing custom rails and scoping active/hover indicators cleanly.
9
+
10
+ ## 1.0.4 - 2026-02-17
11
+
12
+ - Fixed related-posts HTML structure to render valid list markup.
13
+ - Restored sidebar TOC behavior and styling via Tocbot runtime integration.
14
+ - Added Tailwind-first vanilla table engine for `pretty_table` pages when Bootstrap compatibility is disabled.
15
+ - Replaced remaining jQuery-dependent runtime scripts (masonry, jupyter link handling) with vanilla JS.
16
+ - Improved project hover lift, teaching calendar toggle UX, and schedule/table styling parity.
17
+
3
18
  ## 1.0.3 - 2026-02-17
4
19
 
5
20
  - Restricted gem packaging to tracked runtime files to prevent accidental inclusion of local/untracked artifacts.
@@ -3,9 +3,11 @@
3
3
  <h2 class="mb-3"><i class="fas fa-calendar-alt"></i> Upcoming Events</h2>
4
4
 
5
5
  <button
6
- class="btn btn-sm btn-outline-primary mb-3"
6
+ class="btn btn-sm btn-outline-primary mb-3 calendar-toggle-btn"
7
+ type="button"
7
8
  onclick="toggleCalendar()"
8
9
  id="calendar-toggle-btn"
10
+ aria-pressed="false"
9
11
  >
10
12
  Show Calendar
11
13
  </button>
@@ -33,6 +33,17 @@
33
33
  >
34
34
  {% endif %}
35
35
 
36
+ {% if page.toc and page.toc.sidebar %}
37
+ <!-- Tocbot -->
38
+ <link
39
+ defer
40
+ rel="stylesheet"
41
+ href="{{ site.third_party_libraries.tocbot.url.css }}"
42
+ integrity="{{ site.third_party_libraries.tocbot.integrity.css }}"
43
+ crossorigin="anonymous"
44
+ >
45
+ {% endif %}
46
+
36
47
  <!-- Icons -->
37
48
  {% if site.plugins contains 'al_icons' %}
38
49
  {% include plugins/al_icons_styles.liquid %}
@@ -26,7 +26,7 @@
26
26
  {% endif %}
27
27
  <!-- Navbar Toggle -->
28
28
  <button
29
- class="navbar-toggler collapsed ml-auto"
29
+ class="navbar-toggler collapsed navbar-toggler-main"
30
30
  type="button"
31
31
  data-nav-toggle="navbarNav"
32
32
  aria-controls="navbarNav"
@@ -39,8 +39,8 @@
39
39
  <span class="icon-bar bottom-bar"></span>
40
40
  </button>
41
41
 
42
- <div class="collapse navbar-collapse text-right" id="navbarNav">
43
- <ul class="navbar-nav ml-auto flex-nowrap">
42
+ <div class="collapse navbar-collapse navbar-collapse-main" id="navbarNav">
43
+ <ul class="navbar-nav navbar-menu-list flex-nowrap">
44
44
  {% for page in site.pages %}
45
45
  {% if page.permalink == '/' %} {% assign about_title = page.title %} {% endif %}
46
46
  {% endfor %}
@@ -119,7 +119,7 @@
119
119
  {% if site.search_enabled %}
120
120
  <!-- Search -->
121
121
  <li class="nav-item">
122
- <button id="search-toggle" title="Search" onclick="openSearchModal()">
122
+ <button id="search-toggle" type="button" title="Search" aria-label="Open search">
123
123
  <span class="nav-link">ctrl k <i class="fa-solid fa-magnifying-glass"></i></span>
124
124
  </button>
125
125
  </li>
@@ -127,7 +127,7 @@
127
127
  {% if site.enable_darkmode %}
128
128
  <!-- Toogle theme mode -->
129
129
  <li class="toggle-container">
130
- <button id="light-toggle" title="Change theme">
130
+ <button id="light-toggle" type="button" title="Change theme" aria-label="Change color theme">
131
131
  <i class="fa-half-sun-moon" id="light-toggle-system"></i>
132
132
  <i class="fa-solid fa-moon" id="light-toggle-dark"></i>
133
133
  <i class="fa-solid fa-sun" id="light-toggle-light"></i>
@@ -1,35 +1,42 @@
1
- {% assign have_related_posts = false %}
2
- {% for post in site.related_posts | limit: site.related_blog_posts.max_related %}
3
- {% unless have_related_posts %}
4
- {% assign have_related_posts = true %}
5
- {% if page.layout == 'post' %}
6
- <br>
7
- <hr>
8
- <br>
9
- <ul class="list-disc pl-8"></ul>
10
-
11
- <!-- Adds related posts to the end of an article -->
12
- <h2 class="text-3xl font-semibold mb-4 mt-12">Enjoy Reading This Article?</h2>
13
- {% else %}
14
- <h2 class="text-3xl font-semibold mb-4 mt-12">Enjoy Reading This Article?</h2>
15
- {% endif %}
1
+ {% assign related_posts = site.related_posts | limit: site.related_blog_posts.max_related %}
2
+ {% if related_posts != empty %}
3
+ {% if page.layout == 'post' %}
4
+ <br>
5
+ <hr>
6
+ <br>
7
+ {% endif %}
16
8
 
17
- <p class="mb-2">Here are some more articles you might like to read next:</p>
18
- {% endunless %}
9
+ <h2 class="text-3xl font-semibold mb-4 mt-12">Enjoy Reading This Article?</h2>
10
+ <p class="mb-2">Here are some more articles you might like to read next:</p>
19
11
 
20
- <li class="my-2">
21
- {% if post.redirect == blank %}
22
- <a class="text-pink-700 underline font-semibold hover:text-pink-800" href="{{ post.url | relative_url }}">{{ post.title }}</a>
23
- {% elsif post.redirect contains '://' %}
24
- <a class="text-pink-700 underline font-semibold hover:text-pink-800" href="{{ post.redirect }}" target="_blank">{{ post.title }}</a>
25
- <svg width="1rem" height="1rem" viewBox="0 0 30 30" xmlns="http://www.w3.org/2000/svg">
26
- <path d="M17 13.5v6H5v-12h6m3-3h6v6m0-6-9 9" class="icon_svg-stroke" stroke="#999" stroke-width="1.5" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"></path>
27
- </svg>
28
- {% else %}
29
- <a class="text-pink-700 underline font-semibold hover:text-pink-800" href="{{ post.redirect | relative_url }}">{{ post.title }}</a>
30
- {% endif %}
31
- </li>
32
- {% endfor %}
12
+ <ul class="list-disc pl-7 my-3 space-y-2 marker:text-[var(--global-theme-color)]">
13
+ {% for post in related_posts %}
14
+ <li class="leading-relaxed">
15
+ {% if post.redirect == blank %}
16
+ <a class="underline decoration-1 hover:text-[var(--global-hover-color)]" href="{{ post.url | relative_url }}">
17
+ {{- post.title -}}
18
+ </a>
19
+ {% elsif post.redirect contains '://' %}
20
+ <a
21
+ class="underline decoration-1 hover:text-[var(--global-hover-color)]"
22
+ href="{{ post.redirect }}"
23
+ target="_blank"
24
+ rel="noopener noreferrer"
25
+ >
26
+ {{- post.title -}}
27
+ </a>
28
+ <svg width="1rem" height="1rem" viewBox="0 0 30 30" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
29
+ <path d="M17 13.5v6H5v-12h6m3-3h6v6m0-6-9 9" class="icon_svg-stroke" stroke="#999" stroke-width="1.5" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"></path>
30
+ </svg>
31
+ {% else %}
32
+ <a class="underline decoration-1 hover:text-[var(--global-hover-color)]" href="{{ post.redirect | relative_url }}">
33
+ {{- post.title -}}
34
+ </a>
35
+ {% endif %}
36
+ </li>
37
+ {% endfor %}
38
+ </ul>
39
+ {% endif %}
33
40
  {% if site.newsletter.enabled and site.footer_fixed %}
34
41
  <p class="mb-2" style="margin-top: 1.5rem !important">Subscribe to be notified of future articles:</p>
35
42
  {% if site.plugins contains 'al_newsletter' %}
@@ -60,6 +60,19 @@
60
60
  crossorigin="anonymous"
61
61
  ></script>
62
62
  {% endif %}
63
+ {% if page.pretty_table and site.al_folio.compat.bootstrap.enabled != true %}
64
+ <script defer src="{{ '/assets/js/table-engine.js' | relative_url | bust_file_cache }}" type="text/javascript"></script>
65
+ {% endif %}
66
+
67
+ {% if page.toc and page.toc.sidebar %}
68
+ <!-- Tocbot -->
69
+ <script
70
+ defer
71
+ src="{{ site.third_party_libraries.tocbot.url.js }}"
72
+ integrity="{{ site.third_party_libraries.tocbot.integrity.js }}"
73
+ crossorigin="anonymous"
74
+ ></script>
75
+ {% endif %}
63
76
 
64
77
  <!-- Load Common JS -->
65
78
  <script src="{{ '/assets/js/no_defer.js' | relative_url | bust_file_cache }}"></script>
@@ -29,14 +29,14 @@
29
29
  <div class="col-sm-9">{{ content }}</div>
30
30
  <!-- sidebar, which will move to the top on a small screen -->
31
31
  <div class="col-sm-3">
32
- <nav id="toc-sidebar" class="sticky-top"></nav>
32
+ <nav id="toc-sidebar" class="sticky-top toc toc-sidebar" aria-label="Table of contents"></nav>
33
33
  </div>
34
34
  </div>
35
35
  {% else %}
36
36
  <div class="row">
37
37
  <!-- sidebar, which will move to the top on a small screen -->
38
38
  <div class="col-sm-3">
39
- <nav id="toc-sidebar" class="sticky-top"></nav>
39
+ <nav id="toc-sidebar" class="sticky-top toc toc-sidebar" aria-label="Table of contents"></nav>
40
40
  </div>
41
41
  <!-- main content area -->
42
42
  <div class="col-sm-9">{{ content }}</div>
@@ -53,6 +53,29 @@ ul.task-list input[type="checkbox"] {
53
53
  }
54
54
  }
55
55
 
56
+ .hoverable {
57
+ transition:
58
+ transform 0.22s ease,
59
+ box-shadow 0.22s ease;
60
+ }
61
+
62
+ .hoverable:hover {
63
+ transform: translateY(-4px);
64
+ box-shadow:
65
+ 0 10px 22px rgba(0, 0, 0, 0.12),
66
+ 0 4px 10px rgba(0, 0, 0, 0.1);
67
+ }
68
+
69
+ @media (prefers-reduced-motion: reduce) {
70
+ .hoverable {
71
+ transition: none;
72
+ }
73
+
74
+ .hoverable:hover {
75
+ transform: none;
76
+ }
77
+ }
78
+
56
79
  // Profile
57
80
 
58
81
  .profile {
data/_sass/_navbar.scss CHANGED
@@ -9,6 +9,34 @@
9
9
  border-bottom: 1px solid var(--global-divider-color);
10
10
  background-color: var(--global-bg-color);
11
11
  opacity: 0.95;
12
+
13
+ .navbar-collapse-main {
14
+ justify-content: flex-end;
15
+ text-align: right;
16
+ }
17
+
18
+ .navbar-menu-list {
19
+ margin-left: auto;
20
+ align-items: center;
21
+ }
22
+ }
23
+
24
+ @media (max-width: 575.98px) {
25
+ .navbar {
26
+ .navbar-toggler-main {
27
+ margin-left: auto;
28
+ }
29
+
30
+ .navbar-collapse-main {
31
+ text-align: left;
32
+ }
33
+
34
+ .navbar-menu-list {
35
+ margin-left: 0;
36
+ align-items: flex-start;
37
+ width: 100%;
38
+ }
39
+ }
12
40
  }
13
41
 
14
42
  .navbar .dropdown-menu,
@@ -160,6 +188,7 @@
160
188
  border: 0;
161
189
  background-color: inherit;
162
190
  color: var(--global-text-color);
191
+ cursor: pointer;
163
192
  /* Fix footprint to prevent navbar shifting when icon changes */
164
193
  width: 2rem;
165
194
  height: 2rem;
@@ -183,6 +212,7 @@
183
212
  border: 0;
184
213
  background-color: inherit;
185
214
  color: var(--global-text-color);
215
+ cursor: pointer;
186
216
 
187
217
  &:hover {
188
218
  color: var(--global-hover-color);
@@ -63,6 +63,24 @@
63
63
 
64
64
  .course-schedule {
65
65
  margin-bottom: 30px;
66
+
67
+ table.table {
68
+ margin-top: 0.75rem;
69
+ }
70
+
71
+ table.table thead th {
72
+ background-color: color-mix(in srgb, var(--global-theme-color) 12%, transparent);
73
+ border-top: 0;
74
+ border-bottom: 1px solid var(--global-divider-color);
75
+ color: var(--global-text-color);
76
+ font-weight: 600;
77
+ letter-spacing: 0.01em;
78
+ padding: 0.7rem 1rem;
79
+ }
80
+
81
+ table.table tbody td {
82
+ padding: 0.72rem 1rem;
83
+ }
66
84
  }
67
85
 
68
86
  .schedule-description {
@@ -23,15 +23,16 @@ hr {
23
23
  border-top: 1px solid var(--global-divider-color);
24
24
  }
25
25
 
26
- table {
26
+ table:not(.table) {
27
27
  td,
28
28
  th {
29
29
  font-size: 1rem;
30
- padding: 1px 1rem 1px 0;
30
+ padding: 0.45rem 1rem 0.45rem 0;
31
+ border-top: 1px solid var(--global-divider-color);
31
32
  }
32
33
 
33
34
  th {
34
- font-weight: bold;
35
+ font-weight: 600;
35
36
  }
36
37
  }
37
38
 
@@ -55,6 +55,12 @@ code {
55
55
  word-wrap: break-word;
56
56
  }
57
57
 
58
+ :not(pre) > code {
59
+ font-size: 0.82em;
60
+ font-weight: 400;
61
+ padding: 2px 4px;
62
+ }
63
+
58
64
  // Progress bars
59
65
 
60
66
  progress {
@@ -177,33 +183,176 @@ html.transition *:after {
177
183
  }
178
184
  }
179
185
 
186
+ // Tailwind table engine
187
+
188
+ .af-table-shell {
189
+ margin: 1.1rem 0 1.4rem;
190
+ }
191
+
192
+ .af-table-toolbar {
193
+ display: flex;
194
+ flex-wrap: wrap;
195
+ align-items: center;
196
+ justify-content: space-between;
197
+ gap: 0.7rem;
198
+ margin-bottom: 0.6rem;
199
+ }
200
+
201
+ .af-table-search {
202
+ min-width: min(300px, 100%);
203
+ max-width: 100%;
204
+ border: 1px solid var(--global-divider-color);
205
+ border-radius: 0.4rem;
206
+ background-color: var(--global-bg-color);
207
+ color: var(--global-text-color);
208
+ font-size: 0.94rem;
209
+ line-height: 1.4;
210
+ padding: 0.42rem 0.72rem;
211
+ }
212
+
213
+ .af-table-search:focus-visible {
214
+ outline: 2px solid color-mix(in srgb, var(--global-theme-color) 35%, transparent);
215
+ outline-offset: 1px;
216
+ }
217
+
218
+ .af-table-pagination {
219
+ display: inline-flex;
220
+ align-items: center;
221
+ gap: 0.35rem;
222
+ }
223
+
224
+ .af-table-page-info {
225
+ color: var(--global-text-color);
226
+ font-size: 0.85rem;
227
+ font-weight: 500;
228
+ margin-right: 0.3rem;
229
+ }
230
+
231
+ .af-table-pagination button {
232
+ border: 1px solid var(--global-divider-color);
233
+ background-color: transparent;
234
+ border-radius: 0.35rem;
235
+ color: var(--global-text-color);
236
+ cursor: pointer;
237
+ font-size: 0.85rem;
238
+ font-weight: 500;
239
+ line-height: 1;
240
+ min-width: 2rem;
241
+ padding: 0.38rem 0.56rem;
242
+ transition:
243
+ background-color 0.2s ease,
244
+ color 0.2s ease,
245
+ border-color 0.2s ease;
246
+ }
247
+
248
+ .af-table-pagination button:hover:not(:disabled) {
249
+ background-color: var(--global-theme-color);
250
+ border-color: var(--global-theme-color);
251
+ color: var(--global-hover-text-color);
252
+ }
253
+
254
+ .af-table-pagination button:disabled {
255
+ cursor: default;
256
+ opacity: 0.45;
257
+ }
258
+
259
+ table[data-toggle="table"].af-table-enhanced {
260
+ margin-bottom: 0;
261
+ }
262
+
263
+ table[data-toggle="table"].af-table-enhanced thead th {
264
+ background-color: color-mix(in srgb, var(--global-theme-color) 12%, transparent);
265
+ border-bottom: 1px solid var(--global-divider-color);
266
+ color: var(--global-text-color);
267
+ font-weight: 600;
268
+ letter-spacing: 0.01em;
269
+ }
270
+
271
+ table[data-toggle="table"].af-table-enhanced th.af-sortable {
272
+ cursor: pointer;
273
+ user-select: none;
274
+ }
275
+
276
+ table[data-toggle="table"].af-table-enhanced th.af-sortable:hover {
277
+ color: var(--global-hover-color);
278
+ }
279
+
280
+ table[data-toggle="table"].af-table-enhanced tbody tr.af-row-selected {
281
+ background-color: color-mix(in srgb, var(--global-theme-color) 12%, transparent);
282
+ }
283
+
180
284
  // Table of Contents
181
285
 
182
- nav[data-toggle="toc"] {
286
+ #toc-sidebar {
287
+ z-index: 1;
183
288
  top: 5rem;
184
289
 
185
- .nav .nav > li > a {
186
- font-size: 0.75rem;
290
+ &.toc > .toc-list {
291
+ overflow: hidden;
292
+ position: relative;
293
+ }
294
+
295
+ .toc-list {
296
+ list-style: none;
297
+ margin: 0;
298
+ padding-left: 0.9rem;
299
+ }
300
+
301
+ .toc-list .toc-list {
302
+ margin-top: 0.15rem;
303
+ margin-left: 0.55rem;
304
+ padding-left: 0.8rem;
187
305
  }
188
306
 
189
- .nav > li > a {
307
+ .toc-link {
190
308
  color: var(--global-text-color);
191
- font-size: 0.75rem;
309
+ display: block;
310
+ position: relative;
311
+ font-size: 0.78rem;
312
+ line-height: 1.4;
313
+ margin: 0.12rem 0;
314
+ padding: 0.14rem 0.35rem 0.14rem 0.55rem;
315
+ border-left: 0;
316
+ transition:
317
+ color 0.2s ease,
318
+ font-weight 0.2s ease;
319
+ transform: none;
320
+
321
+ &::before {
322
+ background-color: color-mix(in srgb, var(--global-divider-color) 85%, transparent);
323
+ bottom: 0;
324
+ left: 0;
325
+ margin-top: 0;
326
+ top: 0;
327
+ width: 2px;
328
+ }
192
329
 
193
330
  &:hover {
194
331
  color: var(--global-hover-color);
195
- border-left-color: var(--global-hover-color);
332
+ text-decoration: none;
333
+ transform: none;
334
+
335
+ &::before {
336
+ background-color: var(--global-hover-color);
337
+ }
196
338
  }
197
339
  }
198
340
 
199
- .nav-link.active {
341
+ .toc-link.is-active-link {
200
342
  color: var(--global-theme-color);
201
- border-left-color: var(--global-theme-color);
202
- font-size: 0.75rem;
343
+ font-weight: 600;
344
+ transform: none;
345
+
346
+ &::before {
347
+ background-color: var(--global-theme-color);
348
+ }
203
349
 
204
350
  &:hover {
205
351
  color: var(--global-hover-color);
206
- border-left-color: var(--global-hover-color);
352
+
353
+ &::before {
354
+ background-color: var(--global-hover-color);
355
+ }
207
356
  }
208
357
  }
209
358
  }
@@ -211,7 +360,7 @@ nav[data-toggle="toc"] {
211
360
  /* small screens */
212
361
  @media (max-width: 576px) {
213
362
  /* override stickyness so that the navigation does not follow scrolling */
214
- nav[data-toggle="toc"] {
363
+ #toc-sidebar {
215
364
  visibility: hidden;
216
365
  height: 0;
217
366
  top: 0;
@@ -233,7 +382,7 @@ nav[data-toggle="toc"] {
233
382
  }
234
383
  }
235
384
 
236
- code {
385
+ :not(pre) > code {
237
386
  font-size: 0.875rem;
238
387
  padding: 2px 4px;
239
388
  }
@@ -253,7 +402,7 @@ nav[data-toggle="toc"] {
253
402
  }
254
403
  }
255
404
 
256
- code {
405
+ :not(pre) > code {
257
406
  font-size: 0.8rem;
258
407
  padding: 1px 3px;
259
408
  }
@@ -446,10 +595,6 @@ nav[data-toggle="toc"] {
446
595
  z-index: 999;
447
596
  }
448
597
 
449
- #toc-sidebar {
450
- z-index: 1;
451
- }
452
-
453
598
  .echarts {
454
599
  height: 400px;
455
600
  width: 100%;
@@ -574,18 +719,58 @@ figure.cover {
574
719
  // Calendar
575
720
 
576
721
  .calendar-toggle-btn {
577
- background-color: #007acc;
578
- color: white;
579
- padding: 0.5em 1em;
580
- border: none;
581
- border-radius: 6px;
582
- font-size: 1em;
722
+ align-items: center;
723
+ background-color: transparent;
724
+ border: 1px solid var(--global-theme-color);
725
+ border-radius: 999px;
726
+ color: var(--global-theme-color);
583
727
  cursor: pointer;
584
- transition: background-color 0.3s;
728
+ display: inline-flex;
729
+ font-size: 0.92rem;
730
+ font-weight: 600;
731
+ gap: 0.25rem;
732
+ padding: 0.45rem 0.95rem;
733
+ transition:
734
+ background-color 0.2s ease,
735
+ color 0.2s ease,
736
+ box-shadow 0.2s ease,
737
+ transform 0.16s ease;
738
+ }
739
+
740
+ .calendar-toggle-btn:focus-visible {
741
+ outline: 2px solid color-mix(in srgb, var(--global-theme-color) 45%, transparent);
742
+ outline-offset: 2px;
585
743
  }
586
744
 
587
745
  .calendar-toggle-btn:hover {
588
- background-color: #005fa3;
746
+ background-color: color-mix(in srgb, var(--global-theme-color) 14%, transparent);
747
+ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.09);
748
+ color: var(--global-theme-color);
749
+ transform: translateY(-1px);
750
+ }
751
+
752
+ .calendar-toggle-btn:active {
753
+ transform: translateY(0);
754
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
755
+ }
756
+
757
+ .calendar-toggle-btn[aria-pressed="true"],
758
+ .calendar-toggle-btn.is-open {
759
+ background-color: var(--global-theme-color);
760
+ color: var(--global-hover-text-color);
761
+ border-color: var(--global-theme-color);
762
+ }
763
+
764
+ .calendar-toggle-btn[aria-pressed="true"]:hover,
765
+ .calendar-toggle-btn.is-open:hover {
766
+ background-color: var(--global-hover-color);
767
+ border-color: var(--global-hover-color);
768
+ color: var(--global-hover-text-color);
769
+ }
770
+
771
+ .calendar-toggle-btn.btn-sm {
772
+ cursor: pointer;
773
+ font-size: 0.88rem;
589
774
  }
590
775
 
591
776
  .google-calendar-embed {
@@ -2,9 +2,15 @@
2
2
  function toggleCalendar() {
3
3
  const el = document.getElementById("calendar-container");
4
4
  const btn = document.getElementById("calendar-toggle-btn");
5
+ if (!el || !btn) {
6
+ return;
7
+ }
8
+
5
9
  const isHidden = el.style.display === "none";
6
10
  el.style.display = isHidden ? "block" : "none";
7
11
  btn.innerText = isHidden ? "Hide Calendar" : "Show Calendar";
12
+ btn.setAttribute("aria-pressed", isHidden ? "true" : "false");
13
+ btn.classList.toggle("is-open", isHidden);
8
14
 
9
15
  // Update calendar URL when toggling to ensure correct theme
10
16
  if (isHidden) {
data/assets/js/common.js CHANGED
@@ -45,8 +45,8 @@ document.addEventListener("DOMContentLoaded", () => {
45
45
  });
46
46
 
47
47
  const tocSidebar = document.querySelector("#toc-sidebar");
48
+ const contentRoot = document.querySelector('[role="main"]') || document.querySelector("main") || document.body;
48
49
  const buildSidebarToc = (tocRoot) => {
49
- const contentRoot = document.querySelector("main") || document.body;
50
50
  const headings = Array.from(contentRoot.querySelectorAll("h2, h3")).filter((heading) => {
51
51
  return !heading.hasAttribute("data-toc-skip");
52
52
  });
@@ -56,7 +56,7 @@ document.addEventListener("DOMContentLoaded", () => {
56
56
  }
57
57
 
58
58
  const list = document.createElement("ul");
59
- list.className = "nav";
59
+ list.className = "toc-list";
60
60
 
61
61
  headings.forEach((heading) => {
62
62
  if (!heading.id) {
@@ -68,12 +68,13 @@ document.addEventListener("DOMContentLoaded", () => {
68
68
  }
69
69
 
70
70
  const item = document.createElement("li");
71
+ item.className = "toc-list-item";
71
72
  const link = document.createElement("a");
72
- link.className = "nav-link";
73
+ link.className = "toc-link";
73
74
  link.href = `#${heading.id}`;
74
- link.textContent = heading.textContent.trim();
75
+ link.textContent = heading.dataset.tocText || heading.textContent.trim();
75
76
  if (heading.tagName.toLowerCase() === "h3") {
76
- link.classList.add("ml-4");
77
+ item.classList.add("is-collapsible");
77
78
  }
78
79
 
79
80
  item.appendChild(link);
@@ -88,8 +89,51 @@ document.addEventListener("DOMContentLoaded", () => {
88
89
  heading.setAttribute("data-toc-skip", "");
89
90
  });
90
91
 
91
- if (window.Toc && typeof window.Toc.init === "function") {
92
- window.Toc.init(tocSidebar);
92
+ const headings = Array.from(contentRoot.querySelectorAll("h2, h3")).filter((heading) => !heading.hasAttribute("data-toc-skip"));
93
+ headings.forEach((heading) => {
94
+ if (!heading.id) {
95
+ heading.id = heading.textContent
96
+ .trim()
97
+ .toLowerCase()
98
+ .replace(/[^a-z0-9]+/g, "-")
99
+ .replace(/^-+|-+$/g, "");
100
+ }
101
+ });
102
+
103
+ const applyCustomTocLabels = () => {
104
+ tocSidebar.querySelectorAll(".toc-link").forEach((link) => {
105
+ const anchor = link.getAttribute("href") || "";
106
+ const headingId = decodeURIComponent(anchor.replace(/^#/, ""));
107
+ if (!headingId) {
108
+ return;
109
+ }
110
+ const heading = document.getElementById(headingId);
111
+ const customText = heading?.dataset?.tocText;
112
+ if (customText) {
113
+ link.textContent = customText;
114
+ }
115
+ });
116
+ };
117
+
118
+ if (window.tocbot && typeof window.tocbot.init === "function" && headings.length > 0) {
119
+ if (typeof window.tocbot.destroy === "function") {
120
+ window.tocbot.destroy();
121
+ }
122
+
123
+ window.tocbot.init({
124
+ tocSelector: "#toc-sidebar",
125
+ contentSelector: '[role="main"]',
126
+ headingSelector: "h2, h3",
127
+ ignoreSelector: "[data-toc-skip]",
128
+ hasInnerContainers: true,
129
+ collapseDepth: 6,
130
+ orderedList: false,
131
+ activeLinkClass: "is-active-link",
132
+ scrollSmooth: true,
133
+ scrollSmoothOffset: -80,
134
+ headingsOffset: 80,
135
+ });
136
+ applyCustomTocLabels();
93
137
  } else {
94
138
  buildSidebarToc(tocSidebar);
95
139
  }
@@ -1,18 +1,35 @@
1
- $(document).ready(function () {
2
- // Let external links in jupyter notebooks open in new tab
3
- let jupyterNotebooks = $(".jupyter-notebook-iframe-container");
4
- jupyterNotebooks.each(function () {
5
- let iframeBody = $(this).find("iframe").get(0).contentWindow.document.body;
6
- // Get all <a> elements in the bodyElement
7
- let links = $(iframeBody).find("a");
1
+ document.addEventListener("DOMContentLoaded", () => {
2
+ const notebookIframes = document.querySelectorAll(".jupyter-notebook-iframe-container iframe");
8
3
 
9
- // Loop through each <a> element
10
- links.each(function () {
11
- // Check if the <a> element has an 'href' attribute
12
- if ($(this).attr("href")) {
13
- // Set the 'target' attribute to '_blank' to open the link in a new tab/window
14
- $(this).attr("target", "_blank");
4
+ const updateNotebookLinks = (iframe) => {
5
+ try {
6
+ const iframeDocument = iframe.contentDocument || iframe.contentWindow?.document;
7
+ if (!iframeDocument || !iframeDocument.body) {
8
+ return;
15
9
  }
16
- });
10
+
11
+ iframeDocument.querySelectorAll("a[href]").forEach((link) => {
12
+ link.setAttribute("target", "_blank");
13
+ const currentRel = link.getAttribute("rel") || "";
14
+ const relParts = currentRel.split(/\s+/).filter(Boolean);
15
+ if (!relParts.includes("noopener")) {
16
+ relParts.push("noopener");
17
+ }
18
+ if (!relParts.includes("noreferrer")) {
19
+ relParts.push("noreferrer");
20
+ }
21
+ link.setAttribute("rel", relParts.join(" "));
22
+ });
23
+ } catch (_error) {
24
+ // Cross-origin iframe access is blocked by design.
25
+ }
26
+ };
27
+
28
+ notebookIframes.forEach((iframe) => {
29
+ if (iframe.contentDocument?.readyState === "complete") {
30
+ updateNotebookLinks(iframe);
31
+ }
32
+
33
+ iframe.addEventListener("load", () => updateNotebookLinks(iframe));
17
34
  });
18
35
  });
data/assets/js/masonry.js CHANGED
@@ -1,12 +1,22 @@
1
- $(document).ready(function () {
2
- // Init Masonry
3
- var $grid = $(".grid").masonry({
1
+ document.addEventListener("DOMContentLoaded", () => {
2
+ const grid = document.querySelector(".grid");
3
+ if (!grid || typeof window.Masonry !== "function") {
4
+ return;
5
+ }
6
+
7
+ const masonry = new window.Masonry(grid, {
4
8
  gutter: 10,
5
9
  horizontalOrder: true,
6
10
  itemSelector: ".grid-item",
7
11
  });
8
- // Layout Masonry after each image loads
9
- $grid.imagesLoaded().progress(function () {
10
- $grid.masonry("layout");
11
- });
12
+
13
+ if (typeof window.imagesLoaded === "function") {
14
+ const tracker = window.imagesLoaded(grid);
15
+ if (tracker && typeof tracker.on === "function") {
16
+ tracker.on("progress", () => masonry.layout());
17
+ return;
18
+ }
19
+ }
20
+
21
+ masonry.layout();
12
22
  });
@@ -0,0 +1,328 @@
1
+ document.addEventListener("DOMContentLoaded", () => {
2
+ if (window.alFolio?.compatBootstrap) {
3
+ return;
4
+ }
5
+
6
+ const asBool = (value) => String(value || "").toLowerCase() === "true";
7
+
8
+ const parseSortValue = (value) => {
9
+ if (typeof value === "number") {
10
+ return { type: "number", value };
11
+ }
12
+
13
+ const text = String(value ?? "").trim();
14
+ const numeric = Number(text.replace(/[$,%\s,]/g, ""));
15
+ if (text !== "" && Number.isFinite(numeric) && /^[-+]?[$]?\d/.test(text)) {
16
+ return { type: "number", value: numeric };
17
+ }
18
+
19
+ return { type: "string", value: text.toLowerCase() };
20
+ };
21
+
22
+ class AfTableEngine {
23
+ constructor(table) {
24
+ this.table = table;
25
+ this.columns = Array.from(table.querySelectorAll("thead th")).map((th, index) => ({
26
+ element: th,
27
+ index,
28
+ field: th.dataset.field || `__col_${index}`,
29
+ checkbox: asBool(th.dataset.checkbox),
30
+ sortable: asBool(th.dataset.sortable),
31
+ align: th.dataset.align || "",
32
+ halign: th.dataset.halign || th.dataset.align || "",
33
+ }));
34
+ this.state = {
35
+ allRows: [],
36
+ filteredRows: [],
37
+ search: "",
38
+ page: 1,
39
+ pageSize: Number.parseInt(table.dataset.pageSize || "10", 10) || 10,
40
+ sortField: null,
41
+ sortDirection: "asc",
42
+ };
43
+ this.options = {
44
+ search: asBool(table.dataset.search),
45
+ pagination: asBool(table.dataset.pagination),
46
+ clickToSelect: asBool(table.dataset.clickToSelect),
47
+ dataUrl: table.dataset.url || "",
48
+ };
49
+ this.tbody = table.querySelector("tbody") || table.createTBody();
50
+ }
51
+
52
+ async init() {
53
+ this.table.classList.add("table", "table-hover", "af-table-enhanced");
54
+ this.buildShell();
55
+ this.bindHeaderSort();
56
+ this.state.allRows = await this.loadRows();
57
+ this.applyFiltersAndSort();
58
+ this.render();
59
+ }
60
+
61
+ buildShell() {
62
+ const shell = document.createElement("div");
63
+ shell.className = "af-table-shell";
64
+
65
+ this.table.parentNode.insertBefore(shell, this.table);
66
+ shell.appendChild(this.table);
67
+
68
+ const toolbar = document.createElement("div");
69
+ toolbar.className = "af-table-toolbar";
70
+ shell.insertBefore(toolbar, this.table);
71
+ this.toolbar = toolbar;
72
+
73
+ if (this.options.search) {
74
+ const searchInput = document.createElement("input");
75
+ searchInput.className = "af-table-search";
76
+ searchInput.type = "search";
77
+ searchInput.placeholder = "Search table...";
78
+ searchInput.setAttribute("aria-label", "Search table rows");
79
+ searchInput.addEventListener("input", () => {
80
+ this.state.search = searchInput.value.trim().toLowerCase();
81
+ this.state.page = 1;
82
+ this.applyFiltersAndSort();
83
+ this.render();
84
+ });
85
+ toolbar.appendChild(searchInput);
86
+ }
87
+
88
+ if (this.options.pagination) {
89
+ const pagination = document.createElement("div");
90
+ pagination.className = "af-table-pagination";
91
+
92
+ const info = document.createElement("span");
93
+ info.className = "af-table-page-info";
94
+ pagination.appendChild(info);
95
+ this.pageInfo = info;
96
+
97
+ const prevButton = document.createElement("button");
98
+ prevButton.type = "button";
99
+ prevButton.textContent = "Prev";
100
+ prevButton.addEventListener("click", () => {
101
+ if (this.state.page > 1) {
102
+ this.state.page -= 1;
103
+ this.render();
104
+ }
105
+ });
106
+ pagination.appendChild(prevButton);
107
+ this.prevButton = prevButton;
108
+
109
+ const nextButton = document.createElement("button");
110
+ nextButton.type = "button";
111
+ nextButton.textContent = "Next";
112
+ nextButton.addEventListener("click", () => {
113
+ if (this.state.page < this.totalPages()) {
114
+ this.state.page += 1;
115
+ this.render();
116
+ }
117
+ });
118
+ pagination.appendChild(nextButton);
119
+ this.nextButton = nextButton;
120
+
121
+ this.toolbar.appendChild(pagination);
122
+ }
123
+ }
124
+
125
+ bindHeaderSort() {
126
+ this.columns.forEach((column) => {
127
+ if (!column.sortable || column.checkbox) {
128
+ return;
129
+ }
130
+
131
+ if (column.halign) {
132
+ column.element.style.textAlign = column.halign;
133
+ }
134
+ column.element.classList.add("af-sortable");
135
+ column.element.addEventListener("click", () => {
136
+ if (this.state.sortField === column.field) {
137
+ this.state.sortDirection = this.state.sortDirection === "asc" ? "desc" : "asc";
138
+ } else {
139
+ this.state.sortField = column.field;
140
+ this.state.sortDirection = "asc";
141
+ }
142
+ this.renderHeaders();
143
+ this.applyFiltersAndSort();
144
+ this.render();
145
+ });
146
+ });
147
+ }
148
+
149
+ async loadRows() {
150
+ if (!this.options.dataUrl) {
151
+ return this.readRowsFromMarkup();
152
+ }
153
+
154
+ try {
155
+ const response = await fetch(this.options.dataUrl, { credentials: "same-origin" });
156
+ if (!response.ok) {
157
+ return this.readRowsFromMarkup();
158
+ }
159
+ const payload = await response.json();
160
+ if (!Array.isArray(payload)) {
161
+ return this.readRowsFromMarkup();
162
+ }
163
+ return payload.map((row) => this.normalizeRow(row));
164
+ } catch (_error) {
165
+ return this.readRowsFromMarkup();
166
+ }
167
+ }
168
+
169
+ readRowsFromMarkup() {
170
+ const rows = Array.from(this.tbody.querySelectorAll("tr"));
171
+ return rows.map((tr) => {
172
+ const cells = Array.from(tr.querySelectorAll("td"));
173
+ const row = {};
174
+ this.columns.forEach((column) => {
175
+ const cell = cells[column.index];
176
+ if (column.checkbox) {
177
+ row[column.field] = Boolean(cell?.querySelector('input[type="checkbox"]')?.checked);
178
+ } else {
179
+ row[column.field] = cell ? cell.textContent.trim() : "";
180
+ }
181
+ });
182
+ return row;
183
+ });
184
+ }
185
+
186
+ normalizeRow(row) {
187
+ const normalized = {};
188
+ this.columns.forEach((column) => {
189
+ if (column.checkbox) {
190
+ normalized[column.field] = Boolean(row[column.field]);
191
+ return;
192
+ }
193
+ normalized[column.field] = row[column.field] ?? "";
194
+ });
195
+ return normalized;
196
+ }
197
+
198
+ applyFiltersAndSort() {
199
+ let rows = this.state.allRows.slice();
200
+ if (this.state.search) {
201
+ rows = rows.filter((row) => {
202
+ return this.columns.some((column) => {
203
+ if (column.checkbox) {
204
+ return false;
205
+ }
206
+ const value = row[column.field];
207
+ return String(value ?? "")
208
+ .toLowerCase()
209
+ .includes(this.state.search);
210
+ });
211
+ });
212
+ }
213
+
214
+ if (this.state.sortField) {
215
+ const direction = this.state.sortDirection === "asc" ? 1 : -1;
216
+ const field = this.state.sortField;
217
+ rows.sort((leftRow, rightRow) => {
218
+ const left = parseSortValue(leftRow[field]);
219
+ const right = parseSortValue(rightRow[field]);
220
+ if (left.type === "number" && right.type === "number") {
221
+ return (left.value - right.value) * direction;
222
+ }
223
+ return String(left.value).localeCompare(String(right.value)) * direction;
224
+ });
225
+ }
226
+
227
+ this.state.filteredRows = rows;
228
+ if (this.state.page > this.totalPages()) {
229
+ this.state.page = this.totalPages();
230
+ }
231
+ }
232
+
233
+ totalPages() {
234
+ if (!this.options.pagination) {
235
+ return 1;
236
+ }
237
+ return Math.max(1, Math.ceil(this.state.filteredRows.length / this.state.pageSize));
238
+ }
239
+
240
+ paginatedRows() {
241
+ if (!this.options.pagination) {
242
+ return this.state.filteredRows;
243
+ }
244
+ const from = (this.state.page - 1) * this.state.pageSize;
245
+ return this.state.filteredRows.slice(from, from + this.state.pageSize);
246
+ }
247
+
248
+ renderHeaders() {
249
+ this.columns.forEach((column) => {
250
+ const label = column.element.dataset.afLabel || column.element.textContent.trim();
251
+ column.element.dataset.afLabel = label;
252
+ if (!column.sortable || column.checkbox) {
253
+ return;
254
+ }
255
+
256
+ if (this.state.sortField === column.field) {
257
+ const directionMarker = this.state.sortDirection === "asc" ? " \u2191" : " \u2193";
258
+ column.element.textContent = `${label}${directionMarker}`;
259
+ } else {
260
+ column.element.textContent = label;
261
+ }
262
+ });
263
+ }
264
+
265
+ render() {
266
+ this.renderHeaders();
267
+ this.tbody.replaceChildren();
268
+
269
+ this.paginatedRows().forEach((row) => {
270
+ const tr = document.createElement("tr");
271
+ let checkboxControl = null;
272
+
273
+ this.columns.forEach((column) => {
274
+ const td = document.createElement("td");
275
+ if (column.align) {
276
+ td.style.textAlign = column.align;
277
+ }
278
+
279
+ if (column.checkbox) {
280
+ const checkbox = document.createElement("input");
281
+ checkbox.type = "checkbox";
282
+ checkbox.checked = Boolean(row[column.field]);
283
+ checkbox.setAttribute("aria-label", "Select row");
284
+ checkbox.addEventListener("change", () => {
285
+ row[column.field] = checkbox.checked;
286
+ tr.classList.toggle("af-row-selected", checkbox.checked);
287
+ });
288
+ checkboxControl = checkbox;
289
+ td.appendChild(checkbox);
290
+ tr.classList.toggle("af-row-selected", checkbox.checked);
291
+ } else {
292
+ td.textContent = String(row[column.field] ?? "");
293
+ }
294
+
295
+ tr.appendChild(td);
296
+ });
297
+
298
+ if (this.options.clickToSelect && checkboxControl) {
299
+ tr.addEventListener("click", (event) => {
300
+ if (event.target.closest('a, button, input, label, select, textarea')) {
301
+ return;
302
+ }
303
+ checkboxControl.checked = !checkboxControl.checked;
304
+ checkboxControl.dispatchEvent(new Event("change", { bubbles: true }));
305
+ });
306
+ }
307
+
308
+ this.tbody.appendChild(tr);
309
+ });
310
+
311
+ if (this.options.pagination && this.pageInfo && this.prevButton && this.nextButton) {
312
+ const totalPages = this.totalPages();
313
+ this.pageInfo.textContent = `Page ${this.state.page} / ${totalPages}`;
314
+ this.prevButton.disabled = this.state.page <= 1;
315
+ this.nextButton.disabled = this.state.page >= totalPages;
316
+ }
317
+ }
318
+ }
319
+
320
+ document.querySelectorAll('table[data-toggle="table"]').forEach((table) => {
321
+ if (table.dataset.afTableInitialized === "true") {
322
+ return;
323
+ }
324
+ table.dataset.afTableInitialized = "true";
325
+ const engine = new AfTableEngine(table);
326
+ engine.init();
327
+ });
328
+ });
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AlFolioCore
4
- VERSION = "1.0.3"
4
+ VERSION = "1.0.5"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: al_folio_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - al-folio maintainers
@@ -179,6 +179,7 @@ files:
179
179
  - assets/js/nav-toggle.js
180
180
  - assets/js/no_defer.js
181
181
  - assets/js/progress-bar.js
182
+ - assets/js/table-engine.js
182
183
  - assets/js/tabs.js
183
184
  - assets/js/theme.js
184
185
  - assets/js/tooltips-setup.js