datalog-theme 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +162 -0
- data/CITATION.cff +29 -0
- data/LICENSE +21 -0
- data/README.md +257 -0
- data/_data/academic.yml +217 -0
- data/_data/cdn-integrity.yml +51 -0
- data/_data/config/author.yml +121 -0
- data/_data/config/features.yml +262 -0
- data/_data/config/site.yml +42 -0
- data/_data/config/theme.yml +181 -0
- data/_data/datasets.yml +28 -0
- data/_data/i18n/en.yml +438 -0
- data/_data/i18n/es.yml +438 -0
- data/_data/i18n/pt.yml +438 -0
- data/_data/js_manifest.json +22 -0
- data/_data/js_meta.json +371 -0
- data/_data/navigation.yml +145 -0
- data/_data/projects.yml +41 -0
- data/_data/publications.yml +28 -0
- data/_data/social.yml +73 -0
- data/_data/visualizations.yml +51 -0
- data/_includes/analytics/dashboard.html +149 -0
- data/_includes/analytics.html +9 -0
- data/_includes/components/academic-dashboard.html +347 -0
- data/_includes/components/advanced-search.html +682 -0
- data/_includes/components/api-function.html +94 -0
- data/_includes/components/author-bio.html +166 -0
- data/_includes/components/bookmark-system.html +96 -0
- data/_includes/components/breadcrumbs.html +89 -0
- data/_includes/components/citation-tools.html +94 -0
- data/_includes/components/comments.html +244 -0
- data/_includes/components/content-provenance.html +43 -0
- data/_includes/components/content-recommendations.html +228 -0
- data/_includes/components/difficulty-badge.html +84 -0
- data/_includes/components/email-preferences.html +200 -0
- data/_includes/components/enhanced-code-block.html +212 -0
- data/_includes/components/enhanced-metadata.html +228 -0
- data/_includes/components/enhanced-toc.html +209 -0
- data/_includes/components/hero.html +15 -0
- data/_includes/components/language-switcher.html +396 -0
- data/_includes/components/math-fallback.html +19 -0
- data/_includes/components/navigation-enhancements.html +454 -0
- data/_includes/components/newsletter-signup.html +178 -0
- data/_includes/components/open-science-badges.html +16 -0
- data/_includes/components/package-install.html +194 -0
- data/_includes/components/performance-monitor.html +170 -0
- data/_includes/components/popular-posts.html +233 -0
- data/_includes/components/post-hero.html +62 -0
- data/_includes/components/reading-progress.html +133 -0
- data/_includes/components/reading-time.html +121 -0
- data/_includes/components/responsive-image.html +63 -0
- data/_includes/components/search-facets.html +307 -0
- data/_includes/components/series-navigation.html +124 -0
- data/_includes/components/social-proof.html +34 -0
- data/_includes/components/social-share.html +119 -0
- data/_includes/components/user-preferences.html +566 -0
- data/_includes/components/visualization-card.html +24 -0
- data/_includes/components/viz-table-fallback.html +19 -0
- data/_includes/critical-css/default.html +0 -0
- data/_includes/critical-css/home.html +0 -0
- data/_includes/critical-css/post.html +0 -0
- data/_includes/csp-meta.html +17 -0
- data/_includes/footer/nav-column.html +31 -0
- data/_includes/footer.html +76 -0
- data/_includes/head.html +337 -0
- data/_includes/header/navigation.html +48 -0
- data/_includes/header.html +70 -0
- data/_includes/helpers/array-to-sentence.html +21 -0
- data/_includes/helpers/date-format.html +16 -0
- data/_includes/helpers/link-with-icon.html +26 -0
- data/_includes/layouts/default/article.html +67 -0
- data/_includes/meta/language-attributes.html +25 -0
- data/_includes/meta/math-config.html +21 -0
- data/_includes/meta/schema.html +153 -0
- data/_includes/meta/scripts-loader.html +52 -0
- data/_includes/post/related-posts.html +63 -0
- data/_includes/scripts.html +72 -0
- data/_includes/skip-link.html +1 -0
- data/_includes/toc.html +2 -0
- data/_layouts/archive.html +282 -0
- data/_layouts/dataset.html +52 -0
- data/_layouts/default.html +33 -0
- data/_layouts/home.html +48 -0
- data/_layouts/notebook.html +173 -0
- data/_layouts/package.html +238 -0
- data/_layouts/page.html +16 -0
- data/_layouts/portfolio.html +41 -0
- data/_layouts/post-sidebar.html +183 -0
- data/_layouts/post.html +338 -0
- data/_layouts/project.html +252 -0
- data/_layouts/research.html +504 -0
- data/_plugins/analytics_dashboard.rb +315 -0
- data/_plugins/config_validator.rb +397 -0
- data/_plugins/csp_generator.rb +115 -0
- data/_plugins/datalog_bibliography.rb +19 -0
- data/_plugins/datalog_comments.rb +18 -0
- data/_plugins/datalog_slides.rb +20 -0
- data/_plugins/front_matter_compat.rb +109 -0
- data/_plugins/i18n.rb +160 -0
- data/_plugins/image_optimizer.rb +445 -0
- data/_plugins/math_preprocessor.rb +185 -0
- data/_plugins/notebook_converter.rb +1107 -0
- data/_plugins/plugin_loader.rb +185 -0
- data/_plugins/publications_generator.rb +234 -0
- data/_plugins/reading_time.rb +15 -0
- data/_plugins/search_normalizer.rb +74 -0
- data/_plugins/sri_filter.rb +18 -0
- data/_plugins/toc_filter.rb +68 -0
- data/_plugins/warning_filter.rb +3 -0
- data/_sass/_base.scss +142 -0
- data/_sass/_components.scss +2380 -0
- data/_sass/_header.scss +470 -0
- data/_sass/_interactive.scss +344 -0
- data/_sass/_layout.scss +1760 -0
- data/_sass/_mathematical.scss +368 -0
- data/_sass/_mixins.scss +99 -0
- data/_sass/_package-docs.scss +715 -0
- data/_sass/_phase1-enhancements.scss +721 -0
- data/_sass/_phase3-enhancements.scss +874 -0
- data/_sass/_phase4-enhancements.scss +1214 -0
- data/_sass/_phase5-enhancements.scss +414 -0
- data/_sass/_search.scss +654 -0
- data/_sass/_syntax-highlighting.scss +128 -0
- data/_sass/_theme.scss +79 -0
- data/_sass/_typography.scss +148 -0
- data/_sass/_utilities.scss +334 -0
- data/_sass/_variables.scss +153 -0
- data/_sass/_visualizations.scss +242 -0
- data/assets/css/main.scss +4 -0
- data/assets/img/20220607123041_detail.001.png +0 -0
- data/assets/img/favicons/android-chrome-192x192.png +0 -0
- data/assets/img/favicons/android-chrome-512x512.png +0 -0
- data/assets/img/favicons/apple-touch-icon.png +0 -0
- data/assets/img/favicons/favicon-16x16.png +0 -0
- data/assets/img/favicons/favicon-32x32.png +0 -0
- data/assets/img/favicons/favicon.ico +0 -0
- data/assets/img/favicons/site.webmanifest +21 -0
- data/assets/img/portfolio-placeholder.svg +20 -0
- data/assets/js/academic.js +262 -0
- data/assets/js/analytics-dashboard.js +382 -0
- data/assets/js/core/dark-mode.js +79 -0
- data/assets/js/core/github-cards.js +123 -0
- data/assets/js/core/language-filter.js +69 -0
- data/assets/js/core/navigation.js +184 -0
- data/assets/js/core/scroll-progress.js +45 -0
- data/assets/js/core/search-hotkeys.js +62 -0
- data/assets/js/core/skip-links.js +62 -0
- data/assets/js/loader.js +163 -0
- data/assets/js/main.js +23 -0
- data/assets/js/math.js +818 -0
- data/assets/js/notebook.js +158 -0
- data/assets/js/search/analytics.js +91 -0
- data/assets/js/search/app.js +271 -0
- data/assets/js/search/autocomplete.js +120 -0
- data/assets/js/search/engine.js +260 -0
- data/assets/js/search/filters.js +38 -0
- data/assets/js/search/render.js +217 -0
- data/assets/js/search/utils.js +99 -0
- data/assets/js/search.js +354 -0
- data/assets/js/visualizations.js +816 -0
- data/assets/publications/datalog-publications.bib +8 -0
- data/assets/publications/datalog-publications.ris +9 -0
- data/assets/publications/publications.bib +30 -0
- data/assets/templates/diogo-ribeiro-cv.md +31 -0
- data/assets/templates/diogo-ribeiro-cv.tex +32 -0
- data/bin/datalog +26 -0
- data/datalog-theme.gemspec +72 -0
- data/lib/datalog/cli.rb +563 -0
- data/lib/datalog/plugin_system/dependency_resolver.rb +223 -0
- data/lib/datalog/plugin_system.rb +237 -0
- data/lib/datalog/plugins/citations.rb +182 -0
- data/lib/datalog/plugins/comments.rb +183 -0
- data/lib/datalog/plugins/search.rb +45 -0
- data/lib/datalog/plugins/slides.rb +121 -0
- data/lib/datalog/theme/theme.rb +18 -0
- data/lib/datalog/theme/version.rb +7 -0
- data/lib/datalog/warning_filter.rb +28 -0
- data/lib/datalog-theme.rb +17 -0
- metadata +555 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
Reading Progress Tracker
|
|
3
|
+
Displays a visual indicator of reading progress with time estimate
|
|
4
|
+
|
|
5
|
+
Parameters:
|
|
6
|
+
- position: Position on page (top, bottom, sticky)
|
|
7
|
+
- show_time: Show time remaining estimate (default: true)
|
|
8
|
+
- show_percentage: Show percentage complete (default: true)
|
|
9
|
+
{%- endcomment -%}
|
|
10
|
+
|
|
11
|
+
{%- assign position = include.position | default: "top" -%}
|
|
12
|
+
{%- assign show_time = include.show_time | default: true -%}
|
|
13
|
+
{%- assign show_percentage = include.show_percentage | default: true -%}
|
|
14
|
+
|
|
15
|
+
<div class="reading-progress reading-progress--{{ position }}" data-reading-progress aria-label="Reading progress">
|
|
16
|
+
{%- comment -%} Progress bar {%- endcomment -%}
|
|
17
|
+
<div class="reading-progress__bar">
|
|
18
|
+
<div class="reading-progress__fill"
|
|
19
|
+
data-progress-fill
|
|
20
|
+
role="progressbar"
|
|
21
|
+
aria-valuenow="0"
|
|
22
|
+
aria-valuemin="0"
|
|
23
|
+
aria-valuemax="100"
|
|
24
|
+
aria-label="Reading progress percentage">
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
{%- comment -%} Progress stats {%- endcomment -%}
|
|
29
|
+
<div class="reading-progress__stats">
|
|
30
|
+
{%- if show_percentage -%}
|
|
31
|
+
<span class="reading-progress__percentage" data-progress-percentage aria-live="polite">
|
|
32
|
+
<span data-percentage-value>0</span>% complete
|
|
33
|
+
</span>
|
|
34
|
+
{%- endif -%}
|
|
35
|
+
|
|
36
|
+
{%- if show_time -%}
|
|
37
|
+
<span class="reading-progress__time" data-progress-time aria-live="polite">
|
|
38
|
+
<svg class="reading-progress__icon" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
39
|
+
<circle cx="12" cy="12" r="10"></circle>
|
|
40
|
+
<polyline points="12 6 12 12 16 14"></polyline>
|
|
41
|
+
</svg>
|
|
42
|
+
<span data-time-value>...</span> remaining
|
|
43
|
+
</span>
|
|
44
|
+
{%- endif -%}
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<script>
|
|
49
|
+
(function() {
|
|
50
|
+
'use strict';
|
|
51
|
+
|
|
52
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
53
|
+
const progressContainer = document.querySelector('[data-reading-progress]');
|
|
54
|
+
if (!progressContainer) return;
|
|
55
|
+
|
|
56
|
+
const progressFill = progressContainer.querySelector('[data-progress-fill]');
|
|
57
|
+
const percentageValue = progressContainer.querySelector('[data-percentage-value]');
|
|
58
|
+
const timeValue = progressContainer.querySelector('[data-time-value]');
|
|
59
|
+
|
|
60
|
+
// Get article content for calculation
|
|
61
|
+
const articleContent = document.querySelector('.post-content, .article-content, article, main');
|
|
62
|
+
if (!articleContent) return;
|
|
63
|
+
|
|
64
|
+
// Calculate reading time (words per minute)
|
|
65
|
+
const wordsPerMinute = 200;
|
|
66
|
+
const text = articleContent.textContent || articleContent.innerText;
|
|
67
|
+
const wordCount = text.trim().split(/\s+/).length;
|
|
68
|
+
const totalReadingTime = Math.ceil(wordCount / wordsPerMinute);
|
|
69
|
+
|
|
70
|
+
// Track scroll position
|
|
71
|
+
let ticking = false;
|
|
72
|
+
|
|
73
|
+
function updateProgress() {
|
|
74
|
+
// Get scroll position
|
|
75
|
+
const windowHeight = window.innerHeight;
|
|
76
|
+
const documentHeight = document.documentElement.scrollHeight;
|
|
77
|
+
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
|
78
|
+
|
|
79
|
+
// Calculate progress percentage
|
|
80
|
+
const scrollableDistance = documentHeight - windowHeight;
|
|
81
|
+
const scrollPercentage = scrollableDistance > 0
|
|
82
|
+
? Math.min(Math.max((scrollTop / scrollableDistance) * 100, 0), 100)
|
|
83
|
+
: 0;
|
|
84
|
+
|
|
85
|
+
// Update progress bar
|
|
86
|
+
if (progressFill) {
|
|
87
|
+
progressFill.style.width = scrollPercentage + '%';
|
|
88
|
+
progressFill.setAttribute('aria-valuenow', Math.round(scrollPercentage));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Update percentage text
|
|
92
|
+
if (percentageValue) {
|
|
93
|
+
percentageValue.textContent = Math.round(scrollPercentage);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Update time remaining
|
|
97
|
+
if (timeValue) {
|
|
98
|
+
const timeRemaining = Math.max(
|
|
99
|
+
Math.ceil(totalReadingTime * (1 - scrollPercentage / 100)),
|
|
100
|
+
0
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
if (timeRemaining === 0) {
|
|
104
|
+
timeValue.textContent = 'Complete!';
|
|
105
|
+
} else if (timeRemaining === 1) {
|
|
106
|
+
timeValue.textContent = '1 min';
|
|
107
|
+
} else {
|
|
108
|
+
timeValue.textContent = timeRemaining + ' min';
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
ticking = false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function requestTick() {
|
|
116
|
+
if (!ticking) {
|
|
117
|
+
window.requestAnimationFrame(updateProgress);
|
|
118
|
+
ticking = true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Listen to scroll events
|
|
123
|
+
window.addEventListener('scroll', requestTick, { passive: true });
|
|
124
|
+
window.addEventListener('resize', requestTick, { passive: true });
|
|
125
|
+
|
|
126
|
+
// Initial update
|
|
127
|
+
updateProgress();
|
|
128
|
+
|
|
129
|
+
// Update when images load
|
|
130
|
+
window.addEventListener('load', updateProgress);
|
|
131
|
+
});
|
|
132
|
+
})();
|
|
133
|
+
</script>
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
Reading Time Estimate Component
|
|
3
|
+
Calculates and displays estimated reading time with metadata
|
|
4
|
+
|
|
5
|
+
Parameters:
|
|
6
|
+
- content: Content to analyze (default: page.content)
|
|
7
|
+
- words_per_minute: Reading speed (default: 200)
|
|
8
|
+
- show_word_count: Show word count (default: false)
|
|
9
|
+
- show_icon: Show clock icon (default: true)
|
|
10
|
+
- variant: Display variant (inline, badge, detailed) (default: inline)
|
|
11
|
+
{%- endcomment -%}
|
|
12
|
+
|
|
13
|
+
{%- assign content_to_analyze = include.content | default: page.content -%}
|
|
14
|
+
{%- assign words_per_minute = include.words_per_minute | default: 200 -%}
|
|
15
|
+
{%- assign show_word_count = include.show_word_count | default: false -%}
|
|
16
|
+
{%- assign show_icon = include.show_icon | default: true -%}
|
|
17
|
+
{%- assign variant = include.variant | default: "inline" -%}
|
|
18
|
+
|
|
19
|
+
{%- comment -%} Calculate word count {%- endcomment -%}
|
|
20
|
+
{%- assign words = content_to_analyze | strip_html | number_of_words -%}
|
|
21
|
+
|
|
22
|
+
{%- comment -%} Calculate reading time in minutes {%- endcomment -%}
|
|
23
|
+
{%- assign reading_time_float = words | divided_by: words_per_minute | plus: 0.0 -%}
|
|
24
|
+
{%- assign reading_time = reading_time_float | ceil -%}
|
|
25
|
+
|
|
26
|
+
{%- comment -%} Handle edge cases {%- endcomment -%}
|
|
27
|
+
{%- if reading_time < 1 -%}
|
|
28
|
+
{%- assign reading_time = 1 -%}
|
|
29
|
+
{%- endif -%}
|
|
30
|
+
|
|
31
|
+
{%- comment -%} Format reading time display {%- endcomment -%}
|
|
32
|
+
{%- if reading_time == 1 -%}
|
|
33
|
+
{%- assign time_label = "1 min read" -%}
|
|
34
|
+
{%- else -%}
|
|
35
|
+
{%- assign time_label = reading_time | append: " min read" -%}
|
|
36
|
+
{%- endif -%}
|
|
37
|
+
|
|
38
|
+
{%- comment -%} Calculate additional metrics for detailed variant {%- endcomment -%}
|
|
39
|
+
{%- if variant == "detailed" -%}
|
|
40
|
+
{%- assign hours = reading_time | divided_by: 60 -%}
|
|
41
|
+
{%- assign remaining_minutes = reading_time | modulo: 60 -%}
|
|
42
|
+
|
|
43
|
+
{%- comment -%} Estimate based on different reading speeds {%- endcomment -%}
|
|
44
|
+
{%- assign slow_reading_time = words | divided_by: 150 | ceil -%}
|
|
45
|
+
{%- assign fast_reading_time = words | divided_by: 250 | ceil -%}
|
|
46
|
+
{%- endif -%}
|
|
47
|
+
|
|
48
|
+
<span class="reading-time reading-time--{{ variant }}"
|
|
49
|
+
data-reading-time
|
|
50
|
+
data-words="{{ words }}"
|
|
51
|
+
data-minutes="{{ reading_time }}"
|
|
52
|
+
itemscope
|
|
53
|
+
itemtype="https://schema.org/Article">
|
|
54
|
+
|
|
55
|
+
{%- if show_icon -%}
|
|
56
|
+
<svg class="reading-time__icon"
|
|
57
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
58
|
+
width="16"
|
|
59
|
+
height="16"
|
|
60
|
+
viewBox="0 0 24 24"
|
|
61
|
+
fill="none"
|
|
62
|
+
stroke="currentColor"
|
|
63
|
+
stroke-width="2"
|
|
64
|
+
stroke-linecap="round"
|
|
65
|
+
stroke-linejoin="round"
|
|
66
|
+
aria-hidden="true">
|
|
67
|
+
<circle cx="12" cy="12" r="10"></circle>
|
|
68
|
+
<polyline points="12 6 12 12 16 14"></polyline>
|
|
69
|
+
</svg>
|
|
70
|
+
{%- endif -%}
|
|
71
|
+
|
|
72
|
+
{%- if variant == "detailed" -%}
|
|
73
|
+
<span class="reading-time__details">
|
|
74
|
+
<span class="reading-time__primary">
|
|
75
|
+
<strong class="reading-time__time" itemprop="timeRequired" content="PT{{ reading_time }}M">
|
|
76
|
+
{{ time_label }}
|
|
77
|
+
</strong>
|
|
78
|
+
</span>
|
|
79
|
+
|
|
80
|
+
<span class="reading-time__secondary">
|
|
81
|
+
<span class="reading-time__word-count">
|
|
82
|
+
{{ words }} words
|
|
83
|
+
</span>
|
|
84
|
+
|
|
85
|
+
{%- if hours >= 1 -%}
|
|
86
|
+
<span class="reading-time__alternative">
|
|
87
|
+
({{ hours }}h {{ remaining_minutes }}m)
|
|
88
|
+
</span>
|
|
89
|
+
{%- endif -%}
|
|
90
|
+
</span>
|
|
91
|
+
|
|
92
|
+
<span class="reading-time__range">
|
|
93
|
+
<small>{{ fast_reading_time }}-{{ slow_reading_time }} min depending on speed</small>
|
|
94
|
+
</span>
|
|
95
|
+
</span>
|
|
96
|
+
|
|
97
|
+
{%- elsif variant == "badge" -%}
|
|
98
|
+
<span class="reading-time__badge">
|
|
99
|
+
<span class="reading-time__time" itemprop="timeRequired" content="PT{{ reading_time }}M">
|
|
100
|
+
{{ time_label }}
|
|
101
|
+
</span>
|
|
102
|
+
{%- if show_word_count -%}
|
|
103
|
+
<span class="reading-time__word-count">
|
|
104
|
+
{{ words }} words
|
|
105
|
+
</span>
|
|
106
|
+
{%- endif -%}
|
|
107
|
+
</span>
|
|
108
|
+
|
|
109
|
+
{%- else -%}
|
|
110
|
+
{%- comment -%} Inline variant (default) {%- endcomment -%}
|
|
111
|
+
<span class="reading-time__time" itemprop="timeRequired" content="PT{{ reading_time }}M">
|
|
112
|
+
{{ time_label }}
|
|
113
|
+
</span>
|
|
114
|
+
{%- if show_word_count -%}
|
|
115
|
+
<span class="reading-time__separator" aria-hidden="true">•</span>
|
|
116
|
+
<span class="reading-time__word-count">
|
|
117
|
+
{{ words }} words
|
|
118
|
+
</span>
|
|
119
|
+
{%- endif -%}
|
|
120
|
+
{%- endif -%}
|
|
121
|
+
</span>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{% assign image_src = include.src | to_s | strip %}
|
|
2
|
+
{% assign alt_text = include.alt | to_s %}
|
|
3
|
+
{% if image_src == '' or alt_text == '' %}
|
|
4
|
+
{% comment %}Missing required parameters{% endcomment %}
|
|
5
|
+
{% assign fallback_loading = include.loading | default: site.config.datalog_image_config.lazy_loading | default: 'lazy' %}
|
|
6
|
+
<img src="{{ image_src }}" alt="{{ alt_text }}" loading="{{ fallback_loading }}" decoding="async" />
|
|
7
|
+
{% else %}
|
|
8
|
+
{% assign baseurl = site.baseurl | default: '' %}
|
|
9
|
+
{% assign manifest = site.data.datalog_responsive_images | default: {} %}
|
|
10
|
+
{% assign normalized_src = image_src %}
|
|
11
|
+
{% if normalized_src contains '://' %}
|
|
12
|
+
{% assign responsive_entry = nil %}
|
|
13
|
+
{% else %}
|
|
14
|
+
{% if baseurl != '' %}
|
|
15
|
+
{% assign normalized_src = normalized_src | replace_first: baseurl, '' %}
|
|
16
|
+
{% endif %}
|
|
17
|
+
{% assign site_url = site.url | default: '' %}
|
|
18
|
+
{% if site_url != '' %}
|
|
19
|
+
{% assign normalized_src = normalized_src | replace_first: site_url, '' %}
|
|
20
|
+
{% endif %}
|
|
21
|
+
{% assign normalized_src = normalized_src | prepend: '/' %}
|
|
22
|
+
{% assign normalized_src = normalized_src | replace: '//', '/' %}
|
|
23
|
+
{% assign responsive_entry = manifest[normalized_src] %}
|
|
24
|
+
{% endif %}
|
|
25
|
+
{% assign requested_sizes = include.sizes | default: site.config.datalog_image_config.default_sizes | default: '100vw' %}
|
|
26
|
+
{% assign loading_mode = include.loading | default: site.config.datalog_image_config.lazy_loading | default: 'lazy' %}
|
|
27
|
+
{% if responsive_entry %}
|
|
28
|
+
<picture>
|
|
29
|
+
{% for source in responsive_entry.sources %}
|
|
30
|
+
{% assign srcset_items = '' %}
|
|
31
|
+
{% for variant in source.variants %}
|
|
32
|
+
{% assign variant_url = variant.url | relative_url %}
|
|
33
|
+
{% assign descriptor = variant.width | append: 'w' %}
|
|
34
|
+
{% if forloop.first %}
|
|
35
|
+
{% assign srcset_items = variant_url | append: ' ' | append: descriptor %}
|
|
36
|
+
{% else %}
|
|
37
|
+
{% assign srcset_items = srcset_items | append: ', ' | append: variant_url | append: ' ' | append: descriptor %}
|
|
38
|
+
{% endif %}
|
|
39
|
+
{% endfor %}
|
|
40
|
+
<source type="{{ source.type }}" srcset="{{ srcset_items }}" sizes="{{ requested_sizes }}">
|
|
41
|
+
{% endfor %}
|
|
42
|
+
{% assign fallback_srcset = '' %}
|
|
43
|
+
{% for variant in responsive_entry.fallback.variants %}
|
|
44
|
+
{% assign variant_url = variant.url | relative_url %}
|
|
45
|
+
{% assign descriptor = variant.width | append: 'w' %}
|
|
46
|
+
{% if forloop.first %}
|
|
47
|
+
{% assign fallback_srcset = variant_url | append: ' ' | append: descriptor %}
|
|
48
|
+
{% else %}
|
|
49
|
+
{% assign fallback_srcset = fallback_srcset | append: ', ' | append: variant_url | append: ' ' | append: descriptor %}
|
|
50
|
+
{% endif %}
|
|
51
|
+
{% endfor %}
|
|
52
|
+
{% assign fallback_default = responsive_entry.fallback.variants | last %}
|
|
53
|
+
{% assign fallback_src = fallback_default.url | relative_url %}
|
|
54
|
+
<img src="{{ fallback_src }}" srcset="{{ fallback_srcset }}" sizes="{{ requested_sizes }}" alt="{{ alt_text }}" loading="{{ loading_mode }}" decoding="async"{% if responsive_entry.width %} width="{{ responsive_entry.width }}"{% endif %}{% if responsive_entry.height %} height="{{ responsive_entry.height }}"{% endif %}{% if include.class %} class="{{ include.class }}"{% endif %}>
|
|
55
|
+
</picture>
|
|
56
|
+
{% else %}
|
|
57
|
+
{% assign resolved_src = image_src %}
|
|
58
|
+
{% unless image_src contains '://' %}
|
|
59
|
+
{% assign resolved_src = image_src | relative_url %}
|
|
60
|
+
{% endunless %}
|
|
61
|
+
<img src="{{ resolved_src }}" alt="{{ alt_text }}" sizes="{{ requested_sizes }}" loading="{{ loading_mode }}" decoding="async"{% if include.class %} class="{{ include.class }}"{% endif %}>
|
|
62
|
+
{% endif %}
|
|
63
|
+
{% endif %}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
Advanced Search with Facets/Filters
|
|
3
|
+
Provides filtering by tags, categories, dates, and authors
|
|
4
|
+
|
|
5
|
+
Parameters:
|
|
6
|
+
- show_tags: Show tag filter (default: true)
|
|
7
|
+
- show_categories: Show category filter (default: true)
|
|
8
|
+
- show_dates: Show date filter (default: true)
|
|
9
|
+
- show_authors: Show author filter (default: true)
|
|
10
|
+
- show_difficulty: Show difficulty filter (default: true)
|
|
11
|
+
{%- endcomment -%}
|
|
12
|
+
|
|
13
|
+
{%- assign show_tags = include.show_tags | default: true -%}
|
|
14
|
+
{%- assign show_categories = include.show_categories | default: true -%}
|
|
15
|
+
{%- assign show_dates = include.show_dates | default: true -%}
|
|
16
|
+
{%- assign show_authors = include.show_authors | default: true -%}
|
|
17
|
+
{%- assign show_difficulty = include.show_difficulty | default: true -%}
|
|
18
|
+
|
|
19
|
+
<div class="search-facets" data-search-facets>
|
|
20
|
+
<div class="search-facets__header">
|
|
21
|
+
<h2 class="search-facets__title">Filter & Search</h2>
|
|
22
|
+
<button type="button"
|
|
23
|
+
class="search-facets__reset"
|
|
24
|
+
data-reset-filters
|
|
25
|
+
aria-label="Reset all filters">
|
|
26
|
+
Reset
|
|
27
|
+
</button>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
{%- comment -%} Search input {%- endcomment -%}
|
|
31
|
+
<div class="search-facets__search">
|
|
32
|
+
<label for="search-input" class="visually-hidden">Search posts</label>
|
|
33
|
+
<input type="search"
|
|
34
|
+
id="search-input"
|
|
35
|
+
class="search-facets__input"
|
|
36
|
+
placeholder="Search posts..."
|
|
37
|
+
data-search-input
|
|
38
|
+
aria-label="Search posts">
|
|
39
|
+
<svg class="search-facets__search-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
40
|
+
<circle cx="11" cy="11" r="8"></circle>
|
|
41
|
+
<path d="m21 21-4.35-4.35"></path>
|
|
42
|
+
</svg>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
{%- comment -%} Filter panels {%- endcomment -%}
|
|
46
|
+
<div class="search-facets__panels">
|
|
47
|
+
|
|
48
|
+
{%- if show_tags -%}
|
|
49
|
+
{%- comment -%} Collect all tags {%- endcomment -%}
|
|
50
|
+
{%- assign all_tags = "" | split: "" -%}
|
|
51
|
+
{%- for post in site.posts -%}
|
|
52
|
+
{%- if post.tags -%}
|
|
53
|
+
{%- assign all_tags = all_tags | concat: post.tags -%}
|
|
54
|
+
{%- endif -%}
|
|
55
|
+
{%- endfor -%}
|
|
56
|
+
{%- assign unique_tags = all_tags | uniq | sort -%}
|
|
57
|
+
|
|
58
|
+
{%- if unique_tags.size > 0 -%}
|
|
59
|
+
<details class="search-facets__panel" open>
|
|
60
|
+
<summary class="search-facets__panel-title">
|
|
61
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
62
|
+
<path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"></path>
|
|
63
|
+
<line x1="7" y1="7" x2="7.01" y2="7"></line>
|
|
64
|
+
</svg>
|
|
65
|
+
Tags
|
|
66
|
+
</summary>
|
|
67
|
+
<div class="search-facets__options">
|
|
68
|
+
{%- for tag in unique_tags limit: 20 -%}
|
|
69
|
+
<label class="search-facets__checkbox">
|
|
70
|
+
<input type="checkbox"
|
|
71
|
+
value="{{ tag | slugify }}"
|
|
72
|
+
data-filter="tag"
|
|
73
|
+
aria-label="Filter by {{ tag }}">
|
|
74
|
+
<span>{{ tag }}</span>
|
|
75
|
+
</label>
|
|
76
|
+
{%- endfor -%}
|
|
77
|
+
</div>
|
|
78
|
+
</details>
|
|
79
|
+
{%- endif -%}
|
|
80
|
+
{%- endif -%}
|
|
81
|
+
|
|
82
|
+
{%- if show_categories -%}
|
|
83
|
+
{%- comment -%} Collect all categories {%- endcomment -%}
|
|
84
|
+
{%- assign all_categories = "" | split: "" -%}
|
|
85
|
+
{%- for post in site.posts -%}
|
|
86
|
+
{%- if post.categories -%}
|
|
87
|
+
{%- assign all_categories = all_categories | concat: post.categories -%}
|
|
88
|
+
{%- endif -%}
|
|
89
|
+
{%- endfor -%}
|
|
90
|
+
{%- assign unique_categories = all_categories | uniq | sort -%}
|
|
91
|
+
|
|
92
|
+
{%- if unique_categories.size > 0 -%}
|
|
93
|
+
<details class="search-facets__panel">
|
|
94
|
+
<summary class="search-facets__panel-title">
|
|
95
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
96
|
+
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
|
|
97
|
+
</svg>
|
|
98
|
+
Categories
|
|
99
|
+
</summary>
|
|
100
|
+
<div class="search-facets__options">
|
|
101
|
+
{%- for category in unique_categories -%}
|
|
102
|
+
<label class="search-facets__checkbox">
|
|
103
|
+
<input type="checkbox"
|
|
104
|
+
value="{{ category | slugify }}"
|
|
105
|
+
data-filter="category"
|
|
106
|
+
aria-label="Filter by {{ category }}">
|
|
107
|
+
<span>{{ category }}</span>
|
|
108
|
+
</label>
|
|
109
|
+
{%- endfor -%}
|
|
110
|
+
</div>
|
|
111
|
+
</details>
|
|
112
|
+
{%- endif -%}
|
|
113
|
+
{%- endif -%}
|
|
114
|
+
|
|
115
|
+
{%- if show_difficulty -%}
|
|
116
|
+
<details class="search-facets__panel">
|
|
117
|
+
<summary class="search-facets__panel-title">
|
|
118
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
119
|
+
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path>
|
|
120
|
+
</svg>
|
|
121
|
+
Difficulty
|
|
122
|
+
</summary>
|
|
123
|
+
<div class="search-facets__options">
|
|
124
|
+
<label class="search-facets__checkbox">
|
|
125
|
+
<input type="checkbox" value="beginner" data-filter="difficulty" aria-label="Filter by beginner">
|
|
126
|
+
<span>Beginner</span>
|
|
127
|
+
</label>
|
|
128
|
+
<label class="search-facets__checkbox">
|
|
129
|
+
<input type="checkbox" value="intermediate" data-filter="difficulty" aria-label="Filter by intermediate">
|
|
130
|
+
<span>Intermediate</span>
|
|
131
|
+
</label>
|
|
132
|
+
<label class="search-facets__checkbox">
|
|
133
|
+
<input type="checkbox" value="advanced" data-filter="difficulty" aria-label="Filter by advanced">
|
|
134
|
+
<span>Advanced</span>
|
|
135
|
+
</label>
|
|
136
|
+
<label class="search-facets__checkbox">
|
|
137
|
+
<input type="checkbox" value="expert" data-filter="difficulty" aria-label="Filter by expert">
|
|
138
|
+
<span>Expert</span>
|
|
139
|
+
</label>
|
|
140
|
+
</div>
|
|
141
|
+
</details>
|
|
142
|
+
{%- endif -%}
|
|
143
|
+
|
|
144
|
+
{%- if show_dates -%}
|
|
145
|
+
<details class="search-facets__panel">
|
|
146
|
+
<summary class="search-facets__panel-title">
|
|
147
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
148
|
+
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
|
|
149
|
+
<line x1="16" y1="2" x2="16" y2="6"></line>
|
|
150
|
+
<line x1="8" y1="2" x2="8" y2="6"></line>
|
|
151
|
+
<line x1="3" y1="10" x2="21" y2="10"></line>
|
|
152
|
+
</svg>
|
|
153
|
+
Date Range
|
|
154
|
+
</summary>
|
|
155
|
+
<div class="search-facets__options">
|
|
156
|
+
<label class="search-facets__checkbox">
|
|
157
|
+
<input type="radio" name="date-range" value="week" data-filter="date" aria-label="Past week">
|
|
158
|
+
<span>Past week</span>
|
|
159
|
+
</label>
|
|
160
|
+
<label class="search-facets__checkbox">
|
|
161
|
+
<input type="radio" name="date-range" value="month" data-filter="date" aria-label="Past month">
|
|
162
|
+
<span>Past month</span>
|
|
163
|
+
</label>
|
|
164
|
+
<label class="search-facets__checkbox">
|
|
165
|
+
<input type="radio" name="date-range" value="year" data-filter="date" aria-label="Past year">
|
|
166
|
+
<span>Past year</span>
|
|
167
|
+
</label>
|
|
168
|
+
<label class="search-facets__checkbox">
|
|
169
|
+
<input type="radio" name="date-range" value="all" data-filter="date" checked aria-label="All time">
|
|
170
|
+
<span>All time</span>
|
|
171
|
+
</label>
|
|
172
|
+
</div>
|
|
173
|
+
</details>
|
|
174
|
+
{%- endif -%}
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
{%- comment -%} Results count {%- endcomment -%}
|
|
178
|
+
<div class="search-facets__results" data-results-count aria-live="polite">
|
|
179
|
+
<span class="search-facets__count">{{ site.posts.size }}</span> posts found
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
|
|
183
|
+
<script>
|
|
184
|
+
(function() {
|
|
185
|
+
'use strict';
|
|
186
|
+
|
|
187
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
188
|
+
const facetsContainer = document.querySelector('[data-search-facets]');
|
|
189
|
+
if (!facetsContainer) return;
|
|
190
|
+
|
|
191
|
+
const searchInput = facetsContainer.querySelector('[data-search-input]');
|
|
192
|
+
const resetButton = facetsContainer.querySelector('[data-reset-filters]');
|
|
193
|
+
const resultsCount = facetsContainer.querySelector('[data-results-count] .search-facets__count]');
|
|
194
|
+
const checkboxes = facetsContainer.querySelectorAll('[data-filter]');
|
|
195
|
+
|
|
196
|
+
// Get all posts data
|
|
197
|
+
const posts = Array.from(document.querySelectorAll('[data-post]')).map(post => ({
|
|
198
|
+
element: post,
|
|
199
|
+
title: post.dataset.title?.toLowerCase() || '',
|
|
200
|
+
tags: post.dataset.tags?.toLowerCase().split(',') || [],
|
|
201
|
+
categories: post.dataset.categories?.toLowerCase().split(',') || [],
|
|
202
|
+
difficulty: post.dataset.difficulty?.toLowerCase() || '',
|
|
203
|
+
date: new Date(post.dataset.date || 0),
|
|
204
|
+
excerpt: post.dataset.excerpt?.toLowerCase() || ''
|
|
205
|
+
}));
|
|
206
|
+
|
|
207
|
+
function applyFilters() {
|
|
208
|
+
const searchTerm = searchInput?.value.toLowerCase() || '';
|
|
209
|
+
const activeFilters = {
|
|
210
|
+
tags: [],
|
|
211
|
+
categories: [],
|
|
212
|
+
difficulty: [],
|
|
213
|
+
date: 'all'
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
checkboxes.forEach(cb => {
|
|
217
|
+
if (cb.checked) {
|
|
218
|
+
const filterType = cb.dataset.filter;
|
|
219
|
+
if (filterType === 'date') {
|
|
220
|
+
activeFilters.date = cb.value;
|
|
221
|
+
} else {
|
|
222
|
+
activeFilters[filterType].push(cb.value);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
let visibleCount = 0;
|
|
228
|
+
|
|
229
|
+
posts.forEach(post => {
|
|
230
|
+
let isVisible = true;
|
|
231
|
+
|
|
232
|
+
// Search term filter
|
|
233
|
+
if (searchTerm && !post.title.includes(searchTerm) && !post.excerpt.includes(searchTerm)) {
|
|
234
|
+
isVisible = false;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Tag filter
|
|
238
|
+
if (activeFilters.tags.length > 0) {
|
|
239
|
+
const hasTag = activeFilters.tags.some(tag => post.tags.includes(tag));
|
|
240
|
+
if (!hasTag) isVisible = false;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Category filter
|
|
244
|
+
if (activeFilters.categories.length > 0) {
|
|
245
|
+
const hasCategory = activeFilters.categories.some(cat => post.categories.includes(cat));
|
|
246
|
+
if (!hasCategory) isVisible = false;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Difficulty filter
|
|
250
|
+
if (activeFilters.difficulty.length > 0 && !activeFilters.difficulty.includes(post.difficulty)) {
|
|
251
|
+
isVisible = false;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Date filter
|
|
255
|
+
if (activeFilters.date !== 'all') {
|
|
256
|
+
const now = new Date();
|
|
257
|
+
const cutoff = new Date();
|
|
258
|
+
switch (activeFilters.date) {
|
|
259
|
+
case 'week':
|
|
260
|
+
cutoff.setDate(now.getDate() - 7);
|
|
261
|
+
break;
|
|
262
|
+
case 'month':
|
|
263
|
+
cutoff.setMonth(now.getMonth() - 1);
|
|
264
|
+
break;
|
|
265
|
+
case 'year':
|
|
266
|
+
cutoff.setFullYear(now.getFullYear() - 1);
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
if (post.date < cutoff) isVisible = false;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Show/hide post
|
|
273
|
+
post.element.style.display = isVisible ? '' : 'none';
|
|
274
|
+
if (isVisible) visibleCount++;
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// Update count
|
|
278
|
+
if (resultsCount) {
|
|
279
|
+
resultsCount.textContent = visibleCount;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Event listeners
|
|
284
|
+
if (searchInput) {
|
|
285
|
+
searchInput.addEventListener('input', applyFilters);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
checkboxes.forEach(cb => {
|
|
289
|
+
cb.addEventListener('change', applyFilters);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
if (resetButton) {
|
|
293
|
+
resetButton.addEventListener('click', function() {
|
|
294
|
+
if (searchInput) searchInput.value = '';
|
|
295
|
+
checkboxes.forEach(cb => {
|
|
296
|
+
if (cb.type === 'radio' && cb.value === 'all') {
|
|
297
|
+
cb.checked = true;
|
|
298
|
+
} else {
|
|
299
|
+
cb.checked = false;
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
applyFilters();
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
})();
|
|
307
|
+
</script>
|