jekyll-theme-zer0 1.30.0 → 1.31.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/CHANGELOG.md +171 -0
- data/_data/backlog.yml +30 -7
- data/_data/consumers.yml +99 -0
- data/_data/features.yml +1 -1
- data/_data/theme-manifest.yml +35 -33
- data/_data/ui-text.yml +1 -0
- data/_includes/README.md +1 -1
- data/_includes/analytics/posthog.html +28 -9
- data/_includes/components/README.md +1 -1
- data/_includes/components/background-image.html +7 -1
- data/_includes/components/cookie-consent.html +30 -30
- data/_includes/components/mermaid.html +14 -7
- data/_includes/components/preview-image.html +22 -3
- data/_includes/components/theme-preview-gallery.html +1 -1
- data/_includes/content/intro.html +7 -0
- data/_includes/content/seo.html +8 -0
- data/_includes/content/sitemap.html +220 -263
- data/_includes/core/branding.html +8 -15
- data/_includes/core/head.html +29 -5
- data/_includes/core/header.html +14 -2
- data/_includes/core/tokens-inline.html +16 -1
- data/_includes/navigation/navbar.html +9 -6
- data/_includes/setup/wizard.html +5 -1
- data/_layouts/article.html +15 -3
- data/_layouts/home.html +13 -1
- data/_layouts/note.html +2 -1
- data/_layouts/notebook.html +2 -1
- data/_layouts/root.html +22 -0
- data/_sass/components/_cookie-banner.scss +48 -0
- data/_sass/components/_setup-wizard.scss +19 -1
- data/_sass/components/_ui-enhancements.scss +11 -0
- data/_sass/core/_navbar.scss +137 -70
- data/_sass/core/_sidebar-extras.scss +2 -14
- data/_sass/core/code-copy.scss +27 -8
- data/assets/css/extension-points-demo.css +27 -0
- data/assets/data/wiki-index.json +19 -0
- data/assets/js/auto-hide-nav.js +53 -13
- data/assets/js/code-copy.js +72 -1
- data/assets/js/extension-points-demo.js +48 -0
- data/assets/js/mermaid-diagrams.js +128 -22
- data/assets/js/obsidian-local-graph.js +27 -5
- data/assets/js/setup-wizard.js +7 -2
- data/scripts/ci/test_visual_evidence_autogen.py +64 -2
- data/scripts/ci/visual_evidence_autogen.py +14 -4
- data/scripts/features/install-preview-generator +1 -1
- data/scripts/lib/README.md +12 -0
- data/scripts/lib/preview_generator.py +2 -2
- metadata +4 -2
|
@@ -146,7 +146,7 @@
|
|
|
146
146
|
<div class="row mt-3">
|
|
147
147
|
<div class="col-12">
|
|
148
148
|
<div class="d-flex justify-content-between align-items-center">
|
|
149
|
-
<small class="text-muted">
|
|
149
|
+
<small class="text-muted" aria-live="polite">
|
|
150
150
|
Showing <span id="visibleCount" class="fw-bold">0</span> of
|
|
151
151
|
<span id="totalCount" class="fw-bold">0</span> pages
|
|
152
152
|
</small>
|
|
@@ -225,7 +225,7 @@
|
|
|
225
225
|
data-url="{{ page.url | relative_url }}">
|
|
226
226
|
|
|
227
227
|
<td>
|
|
228
|
-
<span class="badge bg-primary rounded-pill">Pages</span>
|
|
228
|
+
<span class="badge text-bg-primary rounded-pill">Pages</span>
|
|
229
229
|
</td>
|
|
230
230
|
<td>
|
|
231
231
|
<a href="{{ page.url | relative_url }}"
|
|
@@ -252,7 +252,7 @@
|
|
|
252
252
|
<span class="badge bg-secondary me-1">{{ category }}</span>
|
|
253
253
|
{% endfor %}
|
|
254
254
|
{% if page.categories.size > 2 %}
|
|
255
|
-
<span class="badge">+{{ page.categories.size | minus: 2 }}</span>
|
|
255
|
+
<span class="badge text-body-secondary border">+{{ page.categories.size | minus: 2 }}</span>
|
|
256
256
|
{% endif %}
|
|
257
257
|
{% endif %}
|
|
258
258
|
</td>
|
|
@@ -262,7 +262,7 @@
|
|
|
262
262
|
<span class="badge bg-outline-primary me-1">#{{ tag }}</span>
|
|
263
263
|
{% endfor %}
|
|
264
264
|
{% if page.tags.size > 3 %}
|
|
265
|
-
<span class="badge">+{{ page.tags.size | minus: 3 }}</span>
|
|
265
|
+
<span class="badge text-body-secondary border">+{{ page.tags.size | minus: 3 }}</span>
|
|
266
266
|
{% endif %}
|
|
267
267
|
{% endif %}
|
|
268
268
|
</td>
|
|
@@ -298,7 +298,7 @@
|
|
|
298
298
|
data-url="{{ item.url | relative_url }}">
|
|
299
299
|
|
|
300
300
|
<td>
|
|
301
|
-
<span class="badge bg-info rounded-pill">{{ collection.label | capitalize }}</span>
|
|
301
|
+
<span class="badge text-bg-info rounded-pill">{{ collection.label | capitalize }}</span>
|
|
302
302
|
</td>
|
|
303
303
|
<td>
|
|
304
304
|
<a href="{{ item.url | relative_url }}"
|
|
@@ -325,7 +325,7 @@
|
|
|
325
325
|
<span class="badge bg-secondary me-1">{{ category }}</span>
|
|
326
326
|
{% endfor %}
|
|
327
327
|
{% if item.categories.size > 2 %}
|
|
328
|
-
<span class="badge">+{{ item.categories.size | minus: 2 }}</span>
|
|
328
|
+
<span class="badge text-body-secondary border">+{{ item.categories.size | minus: 2 }}</span>
|
|
329
329
|
{% endif %}
|
|
330
330
|
{% endif %}
|
|
331
331
|
</td>
|
|
@@ -335,7 +335,7 @@
|
|
|
335
335
|
<span class="badge bg-outline-primary me-1">#{{ tag }}</span>
|
|
336
336
|
{% endfor %}
|
|
337
337
|
{% if item.tags.size > 3 %}
|
|
338
|
-
<span class="badge">+{{ item.tags.size | minus: 3 }}</span>
|
|
338
|
+
<span class="badge text-body-secondary border">+{{ item.tags.size | minus: 3 }}</span>
|
|
339
339
|
{% endif %}
|
|
340
340
|
{% endif %}
|
|
341
341
|
</td>
|
|
@@ -376,6 +376,11 @@
|
|
|
376
376
|
</div>
|
|
377
377
|
</div>
|
|
378
378
|
|
|
379
|
+
{% comment %} Next page of results (both views); shown by the script only when more matches remain {% endcomment %}
|
|
380
|
+
<div class="text-center mt-3 d-none" id="sitemapShowMore">
|
|
381
|
+
<button type="button" class="btn btn-outline-primary" id="sitemapShowMoreBtn">Show more</button>
|
|
382
|
+
</div>
|
|
383
|
+
|
|
379
384
|
{% comment %} ================================ {% endcomment %}
|
|
380
385
|
{% comment %} ENHANCED SITEMAP FUNCTIONALITY {% endcomment %}
|
|
381
386
|
{% comment %} ================================ {% endcomment %}
|
|
@@ -404,9 +409,11 @@ th:hover .sort-icon {
|
|
|
404
409
|
transition: background-color 0.15s ease;
|
|
405
410
|
}
|
|
406
411
|
|
|
412
|
+
/* Outline tag chip: emphasis text (light #052c65 / dark #6ea8fe) instead of
|
|
413
|
+
#0d6efd, which is 3.97:1 on the light table stripe */
|
|
407
414
|
.badge.bg-outline-primary {
|
|
408
|
-
color:
|
|
409
|
-
border: 1px solid
|
|
415
|
+
color: var(--bs-primary-text-emphasis);
|
|
416
|
+
border: 1px solid var(--bs-primary-border-subtle);
|
|
410
417
|
background-color: transparent;
|
|
411
418
|
}
|
|
412
419
|
|
|
@@ -466,8 +473,18 @@ th:hover .sort-icon {
|
|
|
466
473
|
/**
|
|
467
474
|
* Enhanced Sitemap Functionality
|
|
468
475
|
* Features: Advanced search, sorting, filtering, statistics, and card/table views
|
|
476
|
+
*
|
|
477
|
+
* Paginated: every row is still rendered server-side (so the index works
|
|
478
|
+
* without JS and with ?q= from the search modal's "View all results"), but
|
|
479
|
+
* only PAGE_SIZE matching rows stay in the document; the rest are kept in
|
|
480
|
+
* memory and a "Show more" button appends the next page. Cards are built on
|
|
481
|
+
* the first switch to card view, one page at a time. On it-journey.dev the
|
|
482
|
+
* index (431 rows plus 431 eagerly built cards) put ~19,900 elements on
|
|
483
|
+
* /search/ before the visitor typed anything.
|
|
469
484
|
*/
|
|
470
485
|
class SitemapManager {
|
|
486
|
+
static PAGE_SIZE = 50;
|
|
487
|
+
|
|
471
488
|
constructor() {
|
|
472
489
|
this.searchInput = document.getElementById('searchBar');
|
|
473
490
|
this.collectionFilter = document.getElementById('collectionFilter');
|
|
@@ -483,27 +500,30 @@ class SitemapManager {
|
|
|
483
500
|
this.cardsContainer = document.getElementById('sitemapCardsContainer');
|
|
484
501
|
this.tableViewBtn = document.getElementById('tableView');
|
|
485
502
|
this.cardViewBtn = document.getElementById('cardView');
|
|
486
|
-
|
|
503
|
+
this.showMoreWrap = document.getElementById('sitemapShowMore');
|
|
504
|
+
this.showMoreBtn = document.getElementById('sitemapShowMoreBtn');
|
|
505
|
+
|
|
487
506
|
this.rows = Array.from(this.tbody.querySelectorAll('tr'));
|
|
488
507
|
this.visibleCount = document.getElementById('visibleCount');
|
|
489
508
|
this.totalCount = document.getElementById('totalCount');
|
|
490
|
-
|
|
509
|
+
|
|
491
510
|
this.searchTimeout = null;
|
|
492
511
|
this.collections = new Set();
|
|
493
512
|
this.currentView = 'table';
|
|
494
|
-
|
|
513
|
+
this.matches = this.rows;
|
|
514
|
+
this.limit = SitemapManager.PAGE_SIZE;
|
|
515
|
+
this.cards = new WeakMap(); // row -> card element, built on demand
|
|
516
|
+
|
|
495
517
|
this.init();
|
|
496
518
|
}
|
|
497
|
-
|
|
519
|
+
|
|
498
520
|
init() {
|
|
499
521
|
this.setupCollectionFilter();
|
|
500
|
-
this.updateCounts();
|
|
501
522
|
this.updateStatistics();
|
|
502
|
-
this.generateCards();
|
|
503
523
|
this.bindEvents();
|
|
504
|
-
this.handleUrlParams();
|
|
524
|
+
if (!this.handleUrlParams()) this.refresh();
|
|
505
525
|
}
|
|
506
|
-
|
|
526
|
+
|
|
507
527
|
setupCollectionFilter() {
|
|
508
528
|
// Collect unique collections
|
|
509
529
|
this.rows.forEach(row => {
|
|
@@ -512,7 +532,7 @@ class SitemapManager {
|
|
|
512
532
|
this.collections.add(collection);
|
|
513
533
|
}
|
|
514
534
|
});
|
|
515
|
-
|
|
535
|
+
|
|
516
536
|
// Populate collection filter
|
|
517
537
|
Array.from(this.collections).sort().forEach(collection => {
|
|
518
538
|
const option = document.createElement('option');
|
|
@@ -521,324 +541,265 @@ class SitemapManager {
|
|
|
521
541
|
this.collectionFilter.appendChild(option);
|
|
522
542
|
});
|
|
523
543
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
<div class="
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
</div>
|
|
564
|
-
<div class="card-footer bg-transparent">
|
|
565
|
-
<div class="d-flex justify-content-between align-items-center">
|
|
566
|
-
<div class="btn-group btn-group-sm" role="group">
|
|
567
|
-
<a href="${url}" class="btn btn-outline-primary btn-sm" title="View page">
|
|
568
|
-
<i class="bi bi-eye"></i>
|
|
569
|
-
</a>
|
|
570
|
-
<button class="btn btn-outline-secondary btn-sm copy-url"
|
|
571
|
-
data-url="${location.origin}${url}" title="Copy URL">
|
|
572
|
-
<i class="bi bi-clipboard"></i>
|
|
573
|
-
</button>
|
|
574
|
-
</div>
|
|
575
|
-
${categories ? `<small class="text-muted">${categories.split(',').slice(0,2).join(', ')}</small>` : ''}
|
|
576
|
-
</div>
|
|
544
|
+
|
|
545
|
+
// Build (once) the card for a row. Text goes in through textContent /
|
|
546
|
+
// setAttribute, so a title or description containing quotes or markup
|
|
547
|
+
// can no longer break the card's HTML.
|
|
548
|
+
cardFor(row) {
|
|
549
|
+
let card = this.cards.get(row);
|
|
550
|
+
if (card) return card;
|
|
551
|
+
|
|
552
|
+
const d = row.dataset;
|
|
553
|
+
const collection = d.collection || '';
|
|
554
|
+
const description = d.description || 'No description available';
|
|
555
|
+
const formattedDate = d.date ? new Date(d.date).toLocaleDateString('en-US', {
|
|
556
|
+
year: 'numeric',
|
|
557
|
+
month: 'short',
|
|
558
|
+
day: 'numeric'
|
|
559
|
+
}) : 'Unknown';
|
|
560
|
+
const badgeClass = collection === 'pages' ? 'text-bg-primary' : 'text-bg-info';
|
|
561
|
+
|
|
562
|
+
card = document.createElement('div');
|
|
563
|
+
card.className = 'col sitemap-card';
|
|
564
|
+
card.innerHTML = `
|
|
565
|
+
<div class="card h-100 shadow-sm">
|
|
566
|
+
<div class="card-header d-flex justify-content-between align-items-center">
|
|
567
|
+
<span class="badge ${badgeClass} rounded-pill"></span>
|
|
568
|
+
<small class="text-muted"></small>
|
|
569
|
+
</div>
|
|
570
|
+
<div class="card-body">
|
|
571
|
+
<h3 class="card-title h6"><a class="text-decoration-none"></a></h3>
|
|
572
|
+
<p class="card-text text-muted small"></p>
|
|
573
|
+
</div>
|
|
574
|
+
<div class="card-footer bg-transparent">
|
|
575
|
+
<div class="d-flex justify-content-between align-items-center">
|
|
576
|
+
<div class="btn-group btn-group-sm" role="group">
|
|
577
|
+
<a class="btn btn-outline-primary btn-sm" title="View page" aria-label="View page">
|
|
578
|
+
<i class="bi bi-eye" aria-hidden="true"></i>
|
|
579
|
+
</a>
|
|
580
|
+
<button type="button" class="btn btn-outline-secondary btn-sm copy-url" title="Copy URL" aria-label="Copy URL">
|
|
581
|
+
<i class="bi bi-clipboard" aria-hidden="true"></i>
|
|
582
|
+
</button>
|
|
577
583
|
</div>
|
|
584
|
+
<small class="text-muted sitemap-card-categories"></small>
|
|
578
585
|
</div>
|
|
579
586
|
</div>
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
587
|
+
</div>`;
|
|
588
|
+
card.querySelector('.badge').textContent = collection.charAt(0).toUpperCase() + collection.slice(1);
|
|
589
|
+
card.querySelector('.card-header small').textContent = formattedDate;
|
|
590
|
+
const link = card.querySelector('.card-title a');
|
|
591
|
+
link.textContent = d.title || '';
|
|
592
|
+
link.href = d.url;
|
|
593
|
+
card.querySelector('.card-text').textContent = description.length > 100 ? description.substring(0, 100) + '...' : description;
|
|
594
|
+
card.querySelector('.card-footer a').href = d.url;
|
|
595
|
+
const copy = card.querySelector('.copy-url');
|
|
596
|
+
copy.dataset.url = location.origin + d.url;
|
|
597
|
+
copy.addEventListener('click', (e) => this.copyUrl(e));
|
|
598
|
+
card.querySelector('.sitemap-card-categories').textContent =
|
|
599
|
+
d.categories ? d.categories.split(',').slice(0, 2).join(', ') : '';
|
|
600
|
+
|
|
601
|
+
this.cards.set(row, card);
|
|
602
|
+
return card;
|
|
589
603
|
}
|
|
590
|
-
|
|
604
|
+
|
|
591
605
|
bindEvents() {
|
|
592
606
|
// Search with debouncing
|
|
593
607
|
this.searchInput.addEventListener('input', () => {
|
|
594
608
|
clearTimeout(this.searchTimeout);
|
|
595
609
|
this.searchTimeout = setTimeout(() => this.performSearch(), 300);
|
|
596
610
|
});
|
|
597
|
-
|
|
611
|
+
|
|
598
612
|
this.clearSearchBtn.addEventListener('click', () => {
|
|
599
613
|
this.searchInput.value = '';
|
|
600
614
|
this.performSearch();
|
|
601
615
|
this.searchInput.focus();
|
|
602
616
|
});
|
|
603
|
-
|
|
617
|
+
|
|
604
618
|
// Filters
|
|
605
619
|
this.collectionFilter.addEventListener('change', () => this.applyFilters());
|
|
606
620
|
this.dateFilter.addEventListener('change', () => this.applyFilters());
|
|
607
|
-
|
|
621
|
+
|
|
608
622
|
// Reset filters
|
|
609
623
|
this.resetFiltersBtn.addEventListener('click', () => this.resetFilters());
|
|
610
|
-
|
|
624
|
+
|
|
611
625
|
// Toggle statistics
|
|
612
626
|
this.toggleStatsBtn.addEventListener('click', () => this.toggleStatistics());
|
|
613
|
-
|
|
627
|
+
|
|
614
628
|
// View mode toggle
|
|
615
629
|
this.tableViewBtn.addEventListener('change', () => {
|
|
616
630
|
if (this.tableViewBtn.checked) {
|
|
617
631
|
this.switchToTableView();
|
|
618
632
|
}
|
|
619
633
|
});
|
|
620
|
-
|
|
634
|
+
|
|
621
635
|
this.cardViewBtn.addEventListener('change', () => {
|
|
622
636
|
if (this.cardViewBtn.checked) {
|
|
623
637
|
this.switchToCardView();
|
|
624
638
|
}
|
|
625
639
|
});
|
|
626
|
-
|
|
640
|
+
|
|
641
|
+
// Next page of results
|
|
642
|
+
if (this.showMoreBtn) {
|
|
643
|
+
this.showMoreBtn.addEventListener('click', () => {
|
|
644
|
+
this.limit += SitemapManager.PAGE_SIZE;
|
|
645
|
+
this.render();
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
|
|
627
649
|
// Column sorting (table view only)
|
|
628
650
|
this.table.querySelectorAll('th[data-type]').forEach(header => {
|
|
629
651
|
header.addEventListener('click', () => this.sortTable(header));
|
|
630
652
|
});
|
|
631
|
-
|
|
632
|
-
// Copy URL functionality
|
|
633
|
-
|
|
634
|
-
|
|
653
|
+
|
|
654
|
+
// Copy URL functionality (table rows; cards bind their own)
|
|
655
|
+
this.rows.forEach(row => {
|
|
656
|
+
row.querySelectorAll('.copy-url').forEach(btn => {
|
|
657
|
+
btn.addEventListener('click', (e) => this.copyUrl(e));
|
|
658
|
+
});
|
|
635
659
|
});
|
|
636
|
-
|
|
660
|
+
|
|
637
661
|
// Keyboard shortcuts
|
|
638
662
|
document.addEventListener('keydown', (e) => this.handleKeyboardShortcuts(e));
|
|
639
663
|
}
|
|
640
|
-
|
|
664
|
+
|
|
641
665
|
switchToTableView() {
|
|
642
666
|
this.currentView = 'table';
|
|
643
667
|
this.tableViewContainer.classList.remove('d-none');
|
|
644
668
|
this.cardViewContainer.classList.add('d-none');
|
|
669
|
+
this.render();
|
|
645
670
|
}
|
|
646
|
-
|
|
671
|
+
|
|
647
672
|
switchToCardView() {
|
|
648
673
|
this.currentView = 'card';
|
|
649
674
|
this.tableViewContainer.classList.add('d-none');
|
|
650
675
|
this.cardViewContainer.classList.remove('d-none');
|
|
676
|
+
this.render();
|
|
651
677
|
}
|
|
652
|
-
|
|
678
|
+
|
|
653
679
|
performSearch() {
|
|
654
|
-
const searchTerm = this.searchInput.value.toLowerCase().trim();
|
|
655
|
-
|
|
656
|
-
if (this.currentView === 'table') {
|
|
657
|
-
this.rows.forEach(row => {
|
|
658
|
-
if (!searchTerm) {
|
|
659
|
-
row.style.display = '';
|
|
660
|
-
return;
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
const searchableText = [
|
|
664
|
-
row.dataset.title,
|
|
665
|
-
row.dataset.description,
|
|
666
|
-
row.dataset.categories,
|
|
667
|
-
row.dataset.tags,
|
|
668
|
-
row.dataset.collection
|
|
669
|
-
].join(' ').toLowerCase();
|
|
670
|
-
|
|
671
|
-
row.style.display = searchableText.includes(searchTerm) ? '' : 'none';
|
|
672
|
-
});
|
|
673
|
-
} else {
|
|
674
|
-
// Card view search
|
|
675
|
-
const cards = this.cardsContainer.querySelectorAll('.sitemap-card');
|
|
676
|
-
cards.forEach(card => {
|
|
677
|
-
if (!searchTerm) {
|
|
678
|
-
card.style.display = '';
|
|
679
|
-
return;
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
const searchableText = [
|
|
683
|
-
card.dataset.title,
|
|
684
|
-
card.dataset.description,
|
|
685
|
-
card.dataset.categories,
|
|
686
|
-
card.dataset.tags,
|
|
687
|
-
card.dataset.collection
|
|
688
|
-
].join(' ').toLowerCase();
|
|
689
|
-
|
|
690
|
-
card.style.display = searchableText.includes(searchTerm) ? '' : 'none';
|
|
691
|
-
});
|
|
692
|
-
}
|
|
693
|
-
|
|
694
680
|
this.applyFilters();
|
|
695
681
|
}
|
|
696
|
-
|
|
682
|
+
|
|
683
|
+
rowMatches(row, searchTerm, collectionFilter, dateFilter) {
|
|
684
|
+
if (searchTerm) {
|
|
685
|
+
const searchableText = [
|
|
686
|
+
row.dataset.title,
|
|
687
|
+
row.dataset.description,
|
|
688
|
+
row.dataset.categories,
|
|
689
|
+
row.dataset.tags,
|
|
690
|
+
row.dataset.collection
|
|
691
|
+
].join(' ').toLowerCase();
|
|
692
|
+
if (!searchableText.includes(searchTerm)) return false;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
if (collectionFilter && row.dataset.collection !== collectionFilter) return false;
|
|
696
|
+
|
|
697
|
+
if (dateFilter) {
|
|
698
|
+
const rowDate = new Date(row.dataset.date);
|
|
699
|
+
switch (dateFilter) {
|
|
700
|
+
case 'today':
|
|
701
|
+
return this.isSameDay(rowDate, new Date());
|
|
702
|
+
case 'week':
|
|
703
|
+
return this.isWithinDays(rowDate, 7);
|
|
704
|
+
case 'month':
|
|
705
|
+
return this.isWithinDays(rowDate, 30);
|
|
706
|
+
case 'year':
|
|
707
|
+
return this.isWithinDays(rowDate, 365);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return true;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// Recompute the matching rows from search + filters, back to page one.
|
|
697
714
|
applyFilters() {
|
|
715
|
+
const searchTerm = this.searchInput.value.toLowerCase().trim();
|
|
698
716
|
const collectionFilter = this.collectionFilter.value;
|
|
699
717
|
const dateFilter = this.dateFilter.value;
|
|
700
|
-
|
|
718
|
+
this.matches = this.rows.filter(row => this.rowMatches(row, searchTerm, collectionFilter, dateFilter));
|
|
719
|
+
this.limit = SitemapManager.PAGE_SIZE;
|
|
720
|
+
this.render();
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
refresh() {
|
|
724
|
+
this.applyFilters();
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Put the first `limit` matches (in the current sort order) into the
|
|
728
|
+
// active view; everything else stays out of the document.
|
|
729
|
+
render() {
|
|
730
|
+
const shown = this.matches.slice(0, this.limit);
|
|
701
731
|
if (this.currentView === 'table') {
|
|
702
|
-
this.
|
|
703
|
-
let show = true;
|
|
704
|
-
|
|
705
|
-
// Skip if already hidden by search
|
|
706
|
-
if (row.style.display === 'none') return;
|
|
707
|
-
|
|
708
|
-
// Collection filter
|
|
709
|
-
if (collectionFilter && row.dataset.collection !== collectionFilter) {
|
|
710
|
-
show = false;
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
// Date filter
|
|
714
|
-
if (dateFilter && show) {
|
|
715
|
-
const rowDate = new Date(row.dataset.date);
|
|
716
|
-
const now = new Date();
|
|
717
|
-
|
|
718
|
-
switch (dateFilter) {
|
|
719
|
-
case 'today':
|
|
720
|
-
show = this.isSameDay(rowDate, now);
|
|
721
|
-
break;
|
|
722
|
-
case 'week':
|
|
723
|
-
show = this.isWithinDays(rowDate, 7);
|
|
724
|
-
break;
|
|
725
|
-
case 'month':
|
|
726
|
-
show = this.isWithinDays(rowDate, 30);
|
|
727
|
-
break;
|
|
728
|
-
case 'year':
|
|
729
|
-
show = this.isWithinDays(rowDate, 365);
|
|
730
|
-
break;
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
row.style.display = show ? '' : 'none';
|
|
735
|
-
});
|
|
732
|
+
this.tbody.replaceChildren(...shown);
|
|
736
733
|
} else {
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
// Collection filter
|
|
746
|
-
if (collectionFilter && card.dataset.collection !== collectionFilter) {
|
|
747
|
-
show = false;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
// Date filter
|
|
751
|
-
if (dateFilter && show) {
|
|
752
|
-
const cardDate = new Date(card.dataset.date);
|
|
753
|
-
const now = new Date();
|
|
754
|
-
|
|
755
|
-
switch (dateFilter) {
|
|
756
|
-
case 'today':
|
|
757
|
-
show = this.isSameDay(cardDate, now);
|
|
758
|
-
break;
|
|
759
|
-
case 'week':
|
|
760
|
-
show = this.isWithinDays(cardDate, 7);
|
|
761
|
-
break;
|
|
762
|
-
case 'month':
|
|
763
|
-
show = this.isWithinDays(cardDate, 30);
|
|
764
|
-
break;
|
|
765
|
-
case 'year':
|
|
766
|
-
show = this.isWithinDays(cardDate, 365);
|
|
767
|
-
break;
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
card.style.display = show ? '' : 'none';
|
|
772
|
-
});
|
|
734
|
+
this.cardsContainer.replaceChildren(...shown.map(row => this.cardFor(row)));
|
|
735
|
+
}
|
|
736
|
+
if (this.showMoreWrap) {
|
|
737
|
+
const remaining = this.matches.length - shown.length;
|
|
738
|
+
this.showMoreWrap.classList.toggle('d-none', remaining <= 0);
|
|
739
|
+
if (this.showMoreBtn && remaining > 0) {
|
|
740
|
+
this.showMoreBtn.textContent = `Show ${Math.min(remaining, SitemapManager.PAGE_SIZE)} more (${remaining} remaining)`;
|
|
741
|
+
}
|
|
773
742
|
}
|
|
774
|
-
|
|
775
|
-
this.updateCounts();
|
|
743
|
+
this.updateCounts(shown.length);
|
|
776
744
|
}
|
|
777
|
-
|
|
745
|
+
|
|
778
746
|
sortTable(header) {
|
|
779
747
|
const headerIndex = Array.from(header.parentNode.children).indexOf(header);
|
|
780
748
|
const type = header.getAttribute('data-type');
|
|
781
749
|
const currentOrder = header.getAttribute('data-order');
|
|
782
750
|
const newOrder = currentOrder === 'asc' ? 'desc' : 'asc';
|
|
783
|
-
|
|
751
|
+
|
|
784
752
|
// Clear all sort icons
|
|
785
753
|
this.table.querySelectorAll('.sort-icon').forEach(icon => {
|
|
786
754
|
icon.className = 'sort-icon bi bi-arrow-down-up ms-1';
|
|
787
755
|
icon.classList.remove('active');
|
|
788
756
|
});
|
|
789
|
-
|
|
757
|
+
|
|
790
758
|
// Update current header
|
|
791
759
|
header.setAttribute('data-order', newOrder);
|
|
792
760
|
const icon = header.querySelector('.sort-icon');
|
|
793
761
|
icon.className = `sort-icon bi bi-arrow-${newOrder === 'asc' ? 'up' : 'down'} ms-1 active`;
|
|
794
|
-
|
|
795
|
-
// Sort
|
|
796
|
-
|
|
762
|
+
|
|
763
|
+
// Sort every row (not just the rendered page), then re-render
|
|
764
|
+
this.rows.sort((a, b) => {
|
|
797
765
|
let aValue = a.children[headerIndex].textContent.trim();
|
|
798
766
|
let bValue = b.children[headerIndex].textContent.trim();
|
|
799
|
-
|
|
767
|
+
|
|
800
768
|
if (type === 'date') {
|
|
801
769
|
aValue = aValue === "Unknown" ? new Date(0) : new Date(aValue);
|
|
802
770
|
bValue = bValue === "Unknown" ? new Date(0) : new Date(bValue);
|
|
803
771
|
return newOrder === 'asc' ? aValue - bValue : bValue - aValue;
|
|
804
772
|
}
|
|
805
|
-
|
|
806
|
-
return newOrder === 'asc' ?
|
|
807
|
-
aValue.localeCompare(bValue) :
|
|
773
|
+
|
|
774
|
+
return newOrder === 'asc' ?
|
|
775
|
+
aValue.localeCompare(bValue) :
|
|
808
776
|
bValue.localeCompare(aValue);
|
|
809
777
|
});
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
778
|
+
|
|
779
|
+
const keep = this.limit;
|
|
780
|
+
this.applyFilters();
|
|
781
|
+
this.limit = keep;
|
|
782
|
+
this.render();
|
|
813
783
|
}
|
|
814
|
-
|
|
815
|
-
updateCounts() {
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
visibleCount = this.rows.filter(row => row.style.display !== 'none').length;
|
|
820
|
-
} else {
|
|
821
|
-
const cards = this.cardsContainer.querySelectorAll('.sitemap-card');
|
|
822
|
-
visibleCount = Array.from(cards).filter(card => card.style.display !== 'none').length;
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
this.visibleCount.textContent = visibleCount;
|
|
826
|
-
this.totalCount.textContent = this.rows.length;
|
|
827
|
-
|
|
784
|
+
|
|
785
|
+
updateCounts(shownCount) {
|
|
786
|
+
this.visibleCount.textContent = shownCount;
|
|
787
|
+
this.totalCount.textContent = this.matches.length;
|
|
788
|
+
|
|
828
789
|
// Update page statistics
|
|
829
790
|
document.getElementById('totalPages').textContent = this.rows.length;
|
|
830
791
|
document.getElementById('totalCollections').textContent = this.collections.size;
|
|
831
792
|
}
|
|
832
|
-
|
|
793
|
+
|
|
833
794
|
updateStatistics() {
|
|
834
795
|
const statsContainer = document.getElementById('collectionStats');
|
|
835
796
|
const collectionCounts = {};
|
|
836
|
-
|
|
797
|
+
|
|
837
798
|
this.rows.forEach(row => {
|
|
838
799
|
const collection = row.dataset.collection;
|
|
839
800
|
collectionCounts[collection] = (collectionCounts[collection] || 0) + 1;
|
|
840
801
|
});
|
|
841
|
-
|
|
802
|
+
|
|
842
803
|
const statsHtml = Object.entries(collectionCounts)
|
|
843
804
|
.sort(([,a], [,b]) => b - a)
|
|
844
805
|
.map(([collection, count]) => `
|
|
@@ -852,10 +813,10 @@ class SitemapManager {
|
|
|
852
813
|
</div>
|
|
853
814
|
</div>
|
|
854
815
|
`).join('');
|
|
855
|
-
|
|
816
|
+
|
|
856
817
|
statsContainer.innerHTML = statsHtml;
|
|
857
818
|
}
|
|
858
|
-
|
|
819
|
+
|
|
859
820
|
toggleStatistics() {
|
|
860
821
|
this.statsSection.classList.toggle('d-none');
|
|
861
822
|
const isVisible = !this.statsSection.classList.contains('d-none');
|
|
@@ -864,36 +825,29 @@ class SitemapManager {
|
|
|
864
825
|
${isVisible ? 'Hide' : 'Stats'}
|
|
865
826
|
`;
|
|
866
827
|
}
|
|
867
|
-
|
|
828
|
+
|
|
868
829
|
resetFilters() {
|
|
869
830
|
this.searchInput.value = '';
|
|
870
831
|
this.collectionFilter.value = '';
|
|
871
832
|
this.dateFilter.value = '';
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
// Reset cards
|
|
877
|
-
const cards = this.cardsContainer.querySelectorAll('.sitemap-card');
|
|
878
|
-
cards.forEach(card => card.style.display = '');
|
|
879
|
-
|
|
880
|
-
this.updateCounts();
|
|
881
|
-
|
|
833
|
+
|
|
834
|
+
this.applyFilters();
|
|
835
|
+
|
|
882
836
|
// Clear URL parameters
|
|
883
837
|
const url = new URL(window.location);
|
|
884
838
|
url.searchParams.delete('q');
|
|
885
839
|
window.history.replaceState({}, document.title, url);
|
|
886
840
|
}
|
|
887
|
-
|
|
841
|
+
|
|
888
842
|
copyUrl(event) {
|
|
889
|
-
const
|
|
843
|
+
const btn = event.currentTarget;
|
|
844
|
+
const url = btn.dataset.url;
|
|
890
845
|
navigator.clipboard.writeText(url).then(() => {
|
|
891
|
-
const btn = event.currentTarget;
|
|
892
846
|
const originalIcon = btn.innerHTML;
|
|
893
847
|
btn.innerHTML = '<i class="bi bi-check"></i>';
|
|
894
848
|
btn.classList.add('btn-success');
|
|
895
849
|
btn.classList.remove('btn-outline-secondary');
|
|
896
|
-
|
|
850
|
+
|
|
897
851
|
setTimeout(() => {
|
|
898
852
|
btn.innerHTML = originalIcon;
|
|
899
853
|
btn.classList.remove('btn-success');
|
|
@@ -901,36 +855,39 @@ class SitemapManager {
|
|
|
901
855
|
}, 1500);
|
|
902
856
|
});
|
|
903
857
|
}
|
|
904
|
-
|
|
858
|
+
|
|
859
|
+
// Returns true when a ?q= query was applied (which renders the view).
|
|
905
860
|
handleUrlParams() {
|
|
906
861
|
const urlParams = new URLSearchParams(window.location.search);
|
|
907
862
|
const searchQuery = urlParams.get('q');
|
|
908
|
-
|
|
863
|
+
|
|
909
864
|
if (searchQuery) {
|
|
910
865
|
this.searchInput.value = searchQuery;
|
|
911
866
|
this.performSearch();
|
|
867
|
+
return true;
|
|
912
868
|
}
|
|
869
|
+
return false;
|
|
913
870
|
}
|
|
914
|
-
|
|
871
|
+
|
|
915
872
|
handleKeyboardShortcuts(event) {
|
|
916
873
|
// Ctrl/Cmd + K to focus search
|
|
917
874
|
if ((event.ctrlKey || event.metaKey) && event.key === 'k') {
|
|
918
875
|
event.preventDefault();
|
|
919
876
|
this.searchInput.focus();
|
|
920
877
|
}
|
|
921
|
-
|
|
878
|
+
|
|
922
879
|
// Escape to clear search
|
|
923
880
|
if (event.key === 'Escape' && document.activeElement === this.searchInput) {
|
|
924
881
|
this.searchInput.value = '';
|
|
925
882
|
this.performSearch();
|
|
926
883
|
}
|
|
927
884
|
}
|
|
928
|
-
|
|
885
|
+
|
|
929
886
|
// Utility functions
|
|
930
887
|
isSameDay(date1, date2) {
|
|
931
888
|
return date1.toDateString() === date2.toDateString();
|
|
932
889
|
}
|
|
933
|
-
|
|
890
|
+
|
|
934
891
|
isWithinDays(date, days) {
|
|
935
892
|
const now = new Date();
|
|
936
893
|
const diffTime = Math.abs(now - date);
|