datalog-theme 0.6.1 → 0.8.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 +175 -0
- data/CITATION.cff +2 -2
- data/README.md +26 -19
- data/_data/cdn-integrity.yml +0 -30
- data/_includes/analytics/dashboard.html +3 -1
- data/_includes/components/api-function.html +20 -1
- data/_includes/components/author-bio.html +2 -2
- data/_includes/components/enhanced-code-block.html +1 -1
- data/_includes/components/hero.html +19 -1
- data/_includes/csp-meta.html +115 -11
- data/_includes/footer.html +19 -20
- data/_includes/head.html +61 -29
- data/_includes/header/navigation.html +12 -15
- data/_includes/header.html +29 -20
- data/_includes/layouts/default/article.html +5 -1
- data/_includes/meta/math-config.html +27 -1
- data/_includes/meta/schema.html +5 -2
- data/_includes/meta/scripts-loader.html +16 -31
- data/_includes/post/related-posts.html +4 -7
- data/_includes/scripts.html +1 -9
- data/_includes/search/index-data.json +131 -0
- data/_includes/search/page.html +136 -0
- data/_layouts/dataset.html +1 -0
- data/_layouts/default.html +16 -8
- data/_layouts/home.html +6 -0
- data/_layouts/notebook.html +1 -0
- data/_layouts/package.html +1 -0
- data/_layouts/portfolio.html +1 -0
- data/_layouts/post.html +20 -5
- data/_layouts/project.html +2 -1
- data/_plugins/analytics_dashboard.rb +9 -3
- data/_plugins/config_validator.rb +12 -7
- data/_plugins/csp_generator.rb +18 -28
- data/_plugins/datalog_bibliography.rb +9 -7
- data/_plugins/datalog_comments.rb +8 -5
- data/_plugins/datalog_slides.rb +9 -8
- data/_plugins/i18n.rb +7 -8
- data/_plugins/image_optimizer.rb +17 -6
- data/_plugins/math_preprocessor.rb +36 -3
- data/_plugins/notebook_converter.rb +23 -43
- data/_plugins/plugin_loader.rb +3 -1
- data/_plugins/publications_generator.rb +8 -2
- data/_plugins/rouge_highlight_filter.rb +42 -0
- data/_plugins/search_code_blocks.rb +30 -0
- data/_plugins/search_normalizer.rb +15 -53
- data/_plugins/search_pages.rb +72 -0
- data/_sass/_academic-dashboard.scss +262 -0
- data/_sass/_base.scss +19 -1
- data/_sass/_components.scss +77 -1144
- data/_sass/_features.scss +17 -0
- data/_sass/_font-fallbacks.scss +43 -0
- data/_sass/_header.scss +10 -0
- data/_sass/_layout.scss +18 -12
- data/_sass/_mathematical.scss +1 -1
- data/_sass/_notebooks.scss +322 -0
- data/_sass/_open-science-badges.scss +56 -0
- data/_sass/{_phase1-enhancements.scss → _post-components.scss} +46 -1
- data/_sass/_search-page.scss +530 -0
- data/_sass/_search.scss +47 -1
- data/_sass/_syntax-highlighting.scss +212 -97
- data/_sass/_theme.scss +42 -18
- data/_sass/_typography.scss +14 -0
- data/_sass/_variables.scss +7 -4
- data/assets/css/main.scss +14 -0
- data/assets/img/hero-detail-640.webp +0 -0
- data/assets/img/hero-detail.webp +0 -0
- data/assets/img/social-card.png +0 -0
- data/assets/js/dist/academic.js +1 -0
- data/assets/js/dist/analytics-dashboard.js +1 -0
- data/assets/js/dist/chunks/chunk-225H5YXE.js +1 -0
- data/assets/js/dist/core.js +1 -0
- data/assets/js/dist/loader.js +1 -0
- data/assets/js/dist/math.js +1 -0
- data/assets/js/dist/notebook.js +1 -0
- data/assets/js/dist/search.js +1 -0
- data/assets/js/dist/visualizations.js +11 -0
- data/assets/js/loader.js +3 -1
- data/datalog-theme.gemspec +43 -22
- data/lib/datalog/cli.rb +53 -15
- data/lib/datalog/plugin_system/dependency_resolver.rb +0 -2
- data/lib/datalog/theme/package.rb +57 -0
- data/lib/datalog/theme/repository_checkout.rb +90 -0
- data/lib/datalog/theme/version.rb +1 -1
- data/lib/datalog/warning_filter.rb +5 -11
- data/lib/datalog-theme.rb +21 -0
- metadata +75 -146
- data/_data/academic.yml +0 -217
- data/_data/config/author.yml +0 -121
- data/_data/config/features.yml +0 -262
- data/_data/config/site.yml +0 -42
- data/_data/config/theme.yml +0 -181
- data/_data/datasets.yml +0 -28
- data/_data/js_meta.json +0 -371
- data/_data/navigation.yml +0 -145
- data/_data/projects.yml +0 -41
- data/_data/publications.yml +0 -28
- data/_data/social.yml +0 -73
- data/_data/visualizations.yml +0 -51
- data/_includes/components/advanced-search.html +0 -682
- data/_includes/components/bookmark-system.html +0 -96
- data/_includes/components/comments.html +0 -244
- data/_includes/components/content-recommendations.html +0 -228
- data/_includes/components/email-preferences.html +0 -200
- data/_includes/components/enhanced-metadata.html +0 -228
- data/_includes/components/language-switcher.html +0 -396
- data/_includes/components/navigation-enhancements.html +0 -454
- data/_includes/components/newsletter-signup.html +0 -178
- data/_includes/components/popular-posts.html +0 -233
- data/_includes/components/reading-progress.html +0 -133
- data/_includes/components/reading-time.html +0 -121
- data/_includes/components/series-navigation.html +0 -124
- data/_includes/components/social-proof.html +0 -34
- data/_includes/components/user-preferences.html +0 -566
- data/_layouts/archive.html +0 -282
- data/_layouts/post-sidebar.html +0 -183
- data/_sass/_phase3-enhancements.scss +0 -874
- data/_sass/_phase4-enhancements.scss +0 -1214
- data/_sass/_phase5-enhancements.scss +0 -414
- data/assets/img/20220607123041_detail.001.png +0 -0
- data/assets/js/academic.js +0 -262
- data/assets/js/analytics-dashboard.js +0 -382
- data/assets/js/core/dark-mode.js +0 -79
- data/assets/js/core/github-cards.js +0 -123
- data/assets/js/core/language-filter.js +0 -69
- data/assets/js/core/navigation.js +0 -184
- data/assets/js/core/scroll-progress.js +0 -45
- data/assets/js/core/search-hotkeys.js +0 -62
- data/assets/js/core/skip-links.js +0 -62
- data/assets/js/main.js +0 -23
- data/assets/js/math.js +0 -818
- data/assets/js/notebook.js +0 -158
- data/assets/js/search/analytics.js +0 -91
- data/assets/js/search/app.js +0 -271
- data/assets/js/search/autocomplete.js +0 -120
- data/assets/js/search/engine.js +0 -260
- data/assets/js/search/filters.js +0 -38
- data/assets/js/search/render.js +0 -217
- data/assets/js/search/utils.js +0 -99
- data/assets/js/search.js +0 -354
- data/assets/js/visualizations.js +0 -816
- data/assets/publications/datalog-publications.bib +0 -8
- data/assets/publications/datalog-publications.ris +0 -9
- data/assets/publications/publications.bib +0 -30
- data/assets/templates/diogo-ribeiro-cv.md +0 -31
- data/assets/templates/diogo-ribeiro-cv.tex +0 -32
- data/lib/datalog/theme/theme.rb +0 -18
|
@@ -1,682 +0,0 @@
|
|
|
1
|
-
{%- comment -%}
|
|
2
|
-
Advanced Search System Component
|
|
3
|
-
Full-text search with filters, highlighting, and sorting
|
|
4
|
-
|
|
5
|
-
Parameters:
|
|
6
|
-
- show_filters: Show filter panel (default: true)
|
|
7
|
-
- show_sort: Show sort options (default: true)
|
|
8
|
-
- show_results_count: Show results count (default: true)
|
|
9
|
-
- highlight_matches: Highlight search matches (default: true)
|
|
10
|
-
- results_per_page: Results per page (default: 10)
|
|
11
|
-
{%- endcomment -%}
|
|
12
|
-
|
|
13
|
-
{%- assign show_filters = include.show_filters | default: true -%}
|
|
14
|
-
{%- assign show_sort = include.show_sort | default: true -%}
|
|
15
|
-
{%- assign show_results_count = include.show_results_count | default: true -%}
|
|
16
|
-
{%- assign highlight_matches = include.highlight_matches | default: true -%}
|
|
17
|
-
{%- assign results_per_page = include.results_per_page | default: 10 -%}
|
|
18
|
-
|
|
19
|
-
<div class="advanced-search"
|
|
20
|
-
data-advanced-search
|
|
21
|
-
role="search"
|
|
22
|
-
aria-label="Advanced search">
|
|
23
|
-
|
|
24
|
-
<div class="advanced-search__header">
|
|
25
|
-
<div class="search-input-wrapper">
|
|
26
|
-
<svg class="search-input-wrapper__icon"
|
|
27
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
28
|
-
width="20"
|
|
29
|
-
height="20"
|
|
30
|
-
viewBox="0 0 24 24"
|
|
31
|
-
fill="none"
|
|
32
|
-
stroke="currentColor"
|
|
33
|
-
stroke-width="2"
|
|
34
|
-
aria-hidden="true">
|
|
35
|
-
<circle cx="11" cy="11" r="8"></circle>
|
|
36
|
-
<path d="m21 21-4.35-4.35"></path>
|
|
37
|
-
</svg>
|
|
38
|
-
|
|
39
|
-
<input type="search"
|
|
40
|
-
class="search-input-wrapper__input"
|
|
41
|
-
data-search-input
|
|
42
|
-
placeholder="Search posts, tags, categories..."
|
|
43
|
-
aria-label="Search"
|
|
44
|
-
autocomplete="off">
|
|
45
|
-
|
|
46
|
-
<button type="button"
|
|
47
|
-
class="search-input-wrapper__clear"
|
|
48
|
-
data-search-clear
|
|
49
|
-
aria-label="Clear search"
|
|
50
|
-
hidden>
|
|
51
|
-
<svg xmlns="http://www.w3.org/2000/svg"
|
|
52
|
-
width="18"
|
|
53
|
-
height="18"
|
|
54
|
-
viewBox="0 0 24 24"
|
|
55
|
-
fill="none"
|
|
56
|
-
stroke="currentColor"
|
|
57
|
-
stroke-width="2"
|
|
58
|
-
aria-hidden="true">
|
|
59
|
-
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
60
|
-
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
61
|
-
</svg>
|
|
62
|
-
</button>
|
|
63
|
-
</div>
|
|
64
|
-
|
|
65
|
-
<button type="button"
|
|
66
|
-
class="advanced-search__filter-toggle"
|
|
67
|
-
data-filter-toggle
|
|
68
|
-
aria-label="Toggle filters"
|
|
69
|
-
aria-expanded="false">
|
|
70
|
-
<svg xmlns="http://www.w3.org/2000/svg"
|
|
71
|
-
width="18"
|
|
72
|
-
height="18"
|
|
73
|
-
viewBox="0 0 24 24"
|
|
74
|
-
fill="none"
|
|
75
|
-
stroke="currentColor"
|
|
76
|
-
stroke-width="2"
|
|
77
|
-
aria-hidden="true">
|
|
78
|
-
<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"></polygon>
|
|
79
|
-
</svg>
|
|
80
|
-
<span>Filters</span>
|
|
81
|
-
<span class="filter-count" data-filter-count hidden>0</span>
|
|
82
|
-
</button>
|
|
83
|
-
</div>
|
|
84
|
-
|
|
85
|
-
{%- if show_filters -%}
|
|
86
|
-
<div class="advanced-search__filters"
|
|
87
|
-
data-search-filters
|
|
88
|
-
hidden>
|
|
89
|
-
|
|
90
|
-
<div class="search-filter">
|
|
91
|
-
<h4 class="search-filter__title">Categories</h4>
|
|
92
|
-
<div class="search-filter__options" data-filter-group="category">
|
|
93
|
-
{%- assign all_categories = site.posts | map: "categories" | join: "," | split: "," | uniq | sort -%}
|
|
94
|
-
{%- for category in all_categories -%}
|
|
95
|
-
{%- if category != "" -%}
|
|
96
|
-
<label class="filter-checkbox">
|
|
97
|
-
<input type="checkbox"
|
|
98
|
-
value="{{ category | slugify }}"
|
|
99
|
-
data-filter="category">
|
|
100
|
-
<span>{{ category }}</span>
|
|
101
|
-
<span class="filter-checkbox__count">
|
|
102
|
-
{%- assign cat_count = 0 -%}
|
|
103
|
-
{%- for post in site.posts -%}
|
|
104
|
-
{%- if post.categories contains category -%}
|
|
105
|
-
{%- assign cat_count = cat_count | plus: 1 -%}
|
|
106
|
-
{%- endif -%}
|
|
107
|
-
{%- endfor -%}
|
|
108
|
-
({{ cat_count }})
|
|
109
|
-
</span>
|
|
110
|
-
</label>
|
|
111
|
-
{%- endif -%}
|
|
112
|
-
{%- endfor -%}
|
|
113
|
-
</div>
|
|
114
|
-
</div>
|
|
115
|
-
|
|
116
|
-
<div class="search-filter">
|
|
117
|
-
<h4 class="search-filter__title">Tags</h4>
|
|
118
|
-
<div class="search-filter__options search-filter__options--scrollable"
|
|
119
|
-
data-filter-group="tag">
|
|
120
|
-
{%- assign all_tags = site.posts | map: "tags" | join: "," | split: "," | uniq | sort -%}
|
|
121
|
-
{%- for tag in all_tags limit: 20 -%}
|
|
122
|
-
{%- if tag != "" -%}
|
|
123
|
-
<label class="filter-checkbox">
|
|
124
|
-
<input type="checkbox"
|
|
125
|
-
value="{{ tag | slugify }}"
|
|
126
|
-
data-filter="tag">
|
|
127
|
-
<span>{{ tag }}</span>
|
|
128
|
-
</label>
|
|
129
|
-
{%- endif -%}
|
|
130
|
-
{%- endfor -%}
|
|
131
|
-
</div>
|
|
132
|
-
</div>
|
|
133
|
-
|
|
134
|
-
<div class="search-filter">
|
|
135
|
-
<h4 class="search-filter__title">Difficulty</h4>
|
|
136
|
-
<div class="search-filter__options" data-filter-group="difficulty">
|
|
137
|
-
{%- assign difficulties = "beginner,intermediate,advanced,expert" | split: "," -%}
|
|
138
|
-
{%- for difficulty in difficulties -%}
|
|
139
|
-
<label class="filter-checkbox">
|
|
140
|
-
<input type="checkbox"
|
|
141
|
-
value="{{ difficulty }}"
|
|
142
|
-
data-filter="difficulty">
|
|
143
|
-
<span>{{ difficulty | capitalize }}</span>
|
|
144
|
-
</label>
|
|
145
|
-
{%- endfor -%}
|
|
146
|
-
</div>
|
|
147
|
-
</div>
|
|
148
|
-
|
|
149
|
-
<div class="search-filter">
|
|
150
|
-
<h4 class="search-filter__title">Date Range</h4>
|
|
151
|
-
<div class="search-filter__options">
|
|
152
|
-
<label class="filter-radio">
|
|
153
|
-
<input type="radio"
|
|
154
|
-
name="dateRange"
|
|
155
|
-
value="all"
|
|
156
|
-
data-filter="date"
|
|
157
|
-
checked>
|
|
158
|
-
<span>All time</span>
|
|
159
|
-
</label>
|
|
160
|
-
<label class="filter-radio">
|
|
161
|
-
<input type="radio"
|
|
162
|
-
name="dateRange"
|
|
163
|
-
value="week"
|
|
164
|
-
data-filter="date">
|
|
165
|
-
<span>Past week</span>
|
|
166
|
-
</label>
|
|
167
|
-
<label class="filter-radio">
|
|
168
|
-
<input type="radio"
|
|
169
|
-
name="dateRange"
|
|
170
|
-
value="month"
|
|
171
|
-
data-filter="date">
|
|
172
|
-
<span>Past month</span>
|
|
173
|
-
</label>
|
|
174
|
-
<label class="filter-radio">
|
|
175
|
-
<input type="radio"
|
|
176
|
-
name="dateRange"
|
|
177
|
-
value="year"
|
|
178
|
-
data-filter="date">
|
|
179
|
-
<span>Past year</span>
|
|
180
|
-
</label>
|
|
181
|
-
</div>
|
|
182
|
-
</div>
|
|
183
|
-
|
|
184
|
-
<div class="search-filter__actions">
|
|
185
|
-
<button type="button"
|
|
186
|
-
class="button button--secondary"
|
|
187
|
-
data-clear-filters>
|
|
188
|
-
Clear all filters
|
|
189
|
-
</button>
|
|
190
|
-
</div>
|
|
191
|
-
</div>
|
|
192
|
-
{%- endif -%}
|
|
193
|
-
|
|
194
|
-
<div class="advanced-search__toolbar">
|
|
195
|
-
{%- if show_results_count -%}
|
|
196
|
-
<div class="search-results-count" data-results-count>
|
|
197
|
-
<span data-count>0</span> results
|
|
198
|
-
</div>
|
|
199
|
-
{%- endif -%}
|
|
200
|
-
|
|
201
|
-
{%- if show_sort -%}
|
|
202
|
-
<div class="search-sort">
|
|
203
|
-
<label for="search-sort-select">Sort by:</label>
|
|
204
|
-
<select id="search-sort-select"
|
|
205
|
-
class="search-sort__select"
|
|
206
|
-
data-sort-select>
|
|
207
|
-
<option value="relevance">Relevance</option>
|
|
208
|
-
<option value="date-desc">Newest first</option>
|
|
209
|
-
<option value="date-asc">Oldest first</option>
|
|
210
|
-
<option value="title">Title (A-Z)</option>
|
|
211
|
-
</select>
|
|
212
|
-
</div>
|
|
213
|
-
{%- endif -%}
|
|
214
|
-
</div>
|
|
215
|
-
|
|
216
|
-
<div class="advanced-search__results" data-search-results>
|
|
217
|
-
<div class="search-empty-state" data-empty-state>
|
|
218
|
-
<svg xmlns="http://www.w3.org/2000/svg"
|
|
219
|
-
width="64"
|
|
220
|
-
height="64"
|
|
221
|
-
viewBox="0 0 24 24"
|
|
222
|
-
fill="none"
|
|
223
|
-
stroke="currentColor"
|
|
224
|
-
stroke-width="1.5"
|
|
225
|
-
aria-hidden="true">
|
|
226
|
-
<circle cx="11" cy="11" r="8"></circle>
|
|
227
|
-
<path d="m21 21-4.35-4.35"></path>
|
|
228
|
-
</svg>
|
|
229
|
-
<h3>Start searching</h3>
|
|
230
|
-
<p>Enter keywords to find relevant posts</p>
|
|
231
|
-
</div>
|
|
232
|
-
|
|
233
|
-
<div class="search-no-results" data-no-results hidden>
|
|
234
|
-
<svg xmlns="http://www.w3.org/2000/svg"
|
|
235
|
-
width="64"
|
|
236
|
-
height="64"
|
|
237
|
-
viewBox="0 0 24 24"
|
|
238
|
-
fill="none"
|
|
239
|
-
stroke="currentColor"
|
|
240
|
-
stroke-width="1.5"
|
|
241
|
-
aria-hidden="true">
|
|
242
|
-
<circle cx="12" cy="12" r="10"></circle>
|
|
243
|
-
<line x1="12" y1="8" x2="12" y2="12"></line>
|
|
244
|
-
<line x1="12" y1="16" x2="12.01" y2="16"></line>
|
|
245
|
-
</svg>
|
|
246
|
-
<h3>No results found</h3>
|
|
247
|
-
<p>Try adjusting your search or filters</p>
|
|
248
|
-
</div>
|
|
249
|
-
|
|
250
|
-
<div class="search-results-list" data-results-list></div>
|
|
251
|
-
|
|
252
|
-
<div class="search-pagination" data-pagination hidden></div>
|
|
253
|
-
</div>
|
|
254
|
-
</div>
|
|
255
|
-
|
|
256
|
-
{%- comment -%} Generate search index {%- endcomment -%}
|
|
257
|
-
<script type="application/json" data-search-index>
|
|
258
|
-
[
|
|
259
|
-
{%- for post in site.posts -%}
|
|
260
|
-
{
|
|
261
|
-
"id": "{{ post.id | slugify }}",
|
|
262
|
-
"title": {{ post.title | jsonify }},
|
|
263
|
-
"url": "{{ post.url | relative_url }}",
|
|
264
|
-
"excerpt": {{ post.excerpt | strip_html | truncatewords: 50 | jsonify }},
|
|
265
|
-
"content": {{ post.content | strip_html | jsonify }},
|
|
266
|
-
"date": "{{ post.date | date_to_xmlschema }}",
|
|
267
|
-
"categories": {{ post.categories | jsonify }},
|
|
268
|
-
"tags": {{ post.tags | jsonify }},
|
|
269
|
-
"difficulty": "{{ post.difficulty | default: '' }}",
|
|
270
|
-
"author": "{{ post.author | default: site.author.name }}",
|
|
271
|
-
"readingTime": {{ post.content | strip_html | number_of_words | divided_by: 200 | ceil }}
|
|
272
|
-
}{% unless forloop.last %},{% endunless %}
|
|
273
|
-
{%- endfor -%}
|
|
274
|
-
]
|
|
275
|
-
</script>
|
|
276
|
-
|
|
277
|
-
<script>
|
|
278
|
-
(function() {
|
|
279
|
-
'use strict';
|
|
280
|
-
|
|
281
|
-
class AdvancedSearch {
|
|
282
|
-
constructor() {
|
|
283
|
-
this.searchIndex = [];
|
|
284
|
-
this.filteredResults = [];
|
|
285
|
-
this.currentQuery = '';
|
|
286
|
-
this.filters = {
|
|
287
|
-
category: [],
|
|
288
|
-
tag: [],
|
|
289
|
-
difficulty: [],
|
|
290
|
-
date: 'all'
|
|
291
|
-
};
|
|
292
|
-
this.sortBy = 'relevance';
|
|
293
|
-
this.currentPage = 1;
|
|
294
|
-
this.resultsPerPage = {{ results_per_page }};
|
|
295
|
-
|
|
296
|
-
this.init();
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
init() {
|
|
300
|
-
this.loadSearchIndex();
|
|
301
|
-
this.cacheElements();
|
|
302
|
-
this.attachEventListeners();
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
loadSearchIndex() {
|
|
306
|
-
const indexEl = document.querySelector('[data-search-index]');
|
|
307
|
-
if (indexEl) {
|
|
308
|
-
try {
|
|
309
|
-
this.searchIndex = JSON.parse(indexEl.textContent);
|
|
310
|
-
} catch (e) {
|
|
311
|
-
console.error('Failed to load search index:', e);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
cacheElements() {
|
|
317
|
-
this.container = document.querySelector('[data-advanced-search]');
|
|
318
|
-
if (!this.container) return;
|
|
319
|
-
|
|
320
|
-
this.input = this.container.querySelector('[data-search-input]');
|
|
321
|
-
this.clearBtn = this.container.querySelector('[data-search-clear]');
|
|
322
|
-
this.filterToggle = this.container.querySelector('[data-filter-toggle]');
|
|
323
|
-
this.filtersPanel = this.container.querySelector('[data-search-filters]');
|
|
324
|
-
this.resultsList = this.container.querySelector('[data-results-list]');
|
|
325
|
-
this.resultsCount = this.container.querySelector('[data-results-count]');
|
|
326
|
-
this.sortSelect = this.container.querySelector('[data-sort-select]');
|
|
327
|
-
this.emptyState = this.container.querySelector('[data-empty-state]');
|
|
328
|
-
this.noResults = this.container.querySelector('[data-no-results]');
|
|
329
|
-
this.pagination = this.container.querySelector('[data-pagination]');
|
|
330
|
-
this.filterCount = this.container.querySelector('[data-filter-count]');
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
attachEventListeners() {
|
|
334
|
-
if (!this.input) return;
|
|
335
|
-
|
|
336
|
-
// Search input
|
|
337
|
-
let searchTimeout;
|
|
338
|
-
this.input.addEventListener('input', (e) => {
|
|
339
|
-
clearTimeout(searchTimeout);
|
|
340
|
-
searchTimeout = setTimeout(() => {
|
|
341
|
-
this.currentQuery = e.target.value.trim();
|
|
342
|
-
this.search();
|
|
343
|
-
}, 300);
|
|
344
|
-
|
|
345
|
-
this.clearBtn.hidden = !e.target.value;
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
// Clear button
|
|
349
|
-
this.clearBtn?.addEventListener('click', () => {
|
|
350
|
-
this.input.value = '';
|
|
351
|
-
this.currentQuery = '';
|
|
352
|
-
this.clearBtn.hidden = true;
|
|
353
|
-
this.search();
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
// Filter toggle
|
|
357
|
-
this.filterToggle?.addEventListener('click', () => {
|
|
358
|
-
const isHidden = this.filtersPanel.hidden;
|
|
359
|
-
this.filtersPanel.hidden = !isHidden;
|
|
360
|
-
this.filterToggle.setAttribute('aria-expanded', !isHidden);
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
// Filter checkboxes
|
|
364
|
-
const filterCheckboxes = this.container.querySelectorAll('[data-filter]');
|
|
365
|
-
filterCheckboxes.forEach(checkbox => {
|
|
366
|
-
checkbox.addEventListener('change', () => {
|
|
367
|
-
this.updateFilters();
|
|
368
|
-
this.search();
|
|
369
|
-
});
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
// Clear filters
|
|
373
|
-
const clearFiltersBtn = this.container.querySelector('[data-clear-filters]');
|
|
374
|
-
clearFiltersBtn?.addEventListener('click', () => {
|
|
375
|
-
this.clearFilters();
|
|
376
|
-
this.search();
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
// Sort
|
|
380
|
-
this.sortSelect?.addEventListener('change', (e) => {
|
|
381
|
-
this.sortBy = e.target.value;
|
|
382
|
-
this.sortResults();
|
|
383
|
-
this.renderResults();
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
updateFilters() {
|
|
388
|
-
// Category filters
|
|
389
|
-
const categoryFilters = Array.from(
|
|
390
|
-
this.container.querySelectorAll('[data-filter="category"]:checked')
|
|
391
|
-
).map(cb => cb.value);
|
|
392
|
-
|
|
393
|
-
// Tag filters
|
|
394
|
-
const tagFilters = Array.from(
|
|
395
|
-
this.container.querySelectorAll('[data-filter="tag"]:checked')
|
|
396
|
-
).map(cb => cb.value);
|
|
397
|
-
|
|
398
|
-
// Difficulty filters
|
|
399
|
-
const difficultyFilters = Array.from(
|
|
400
|
-
this.container.querySelectorAll('[data-filter="difficulty"]:checked')
|
|
401
|
-
).map(cb => cb.value);
|
|
402
|
-
|
|
403
|
-
// Date filter
|
|
404
|
-
const dateFilter = this.container.querySelector('[data-filter="date"]:checked')?.value || 'all';
|
|
405
|
-
|
|
406
|
-
this.filters = {
|
|
407
|
-
category: categoryFilters,
|
|
408
|
-
tag: tagFilters,
|
|
409
|
-
difficulty: difficultyFilters,
|
|
410
|
-
date: dateFilter
|
|
411
|
-
};
|
|
412
|
-
|
|
413
|
-
// Update filter count
|
|
414
|
-
const totalFilters = categoryFilters.length + tagFilters.length + difficultyFilters.length +
|
|
415
|
-
(dateFilter !== 'all' ? 1 : 0);
|
|
416
|
-
if (this.filterCount) {
|
|
417
|
-
this.filterCount.textContent = totalFilters;
|
|
418
|
-
this.filterCount.hidden = totalFilters === 0;
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
clearFilters() {
|
|
423
|
-
const allCheckboxes = this.container.querySelectorAll('[data-filter]:checked');
|
|
424
|
-
allCheckboxes.forEach(cb => {
|
|
425
|
-
if (cb.type === 'checkbox') {
|
|
426
|
-
cb.checked = false;
|
|
427
|
-
} else if (cb.type === 'radio' && cb.value !== 'all') {
|
|
428
|
-
cb.checked = false;
|
|
429
|
-
} else if (cb.value === 'all') {
|
|
430
|
-
cb.checked = true;
|
|
431
|
-
}
|
|
432
|
-
});
|
|
433
|
-
|
|
434
|
-
this.filters = {
|
|
435
|
-
category: [],
|
|
436
|
-
tag: [],
|
|
437
|
-
difficulty: [],
|
|
438
|
-
date: 'all'
|
|
439
|
-
};
|
|
440
|
-
|
|
441
|
-
if (this.filterCount) {
|
|
442
|
-
this.filterCount.hidden = true;
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
search() {
|
|
447
|
-
if (!this.currentQuery && this.isFiltersEmpty()) {
|
|
448
|
-
this.showEmptyState();
|
|
449
|
-
return;
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
// Filter results
|
|
453
|
-
this.filteredResults = this.searchIndex.filter(post => {
|
|
454
|
-
// Text search
|
|
455
|
-
if (this.currentQuery) {
|
|
456
|
-
const query = this.currentQuery.toLowerCase();
|
|
457
|
-
const searchableText = `${post.title} ${post.excerpt} ${post.content} ${post.tags.join(' ')} ${post.categories.join(' ')}`.toLowerCase();
|
|
458
|
-
if (!searchableText.includes(query)) {
|
|
459
|
-
return false;
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// Category filter
|
|
464
|
-
if (this.filters.category.length > 0) {
|
|
465
|
-
const postCategories = post.categories.map(c => c.toLowerCase().replace(/\s+/g, '-'));
|
|
466
|
-
if (!this.filters.category.some(f => postCategories.includes(f))) {
|
|
467
|
-
return false;
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
// Tag filter
|
|
472
|
-
if (this.filters.tag.length > 0) {
|
|
473
|
-
const postTags = post.tags.map(t => t.toLowerCase().replace(/\s+/g, '-'));
|
|
474
|
-
if (!this.filters.tag.some(f => postTags.includes(f))) {
|
|
475
|
-
return false;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
// Difficulty filter
|
|
480
|
-
if (this.filters.difficulty.length > 0) {
|
|
481
|
-
if (!this.filters.difficulty.includes(post.difficulty.toLowerCase())) {
|
|
482
|
-
return false;
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
// Date filter
|
|
487
|
-
if (this.filters.date !== 'all') {
|
|
488
|
-
const postDate = new Date(post.date);
|
|
489
|
-
const now = new Date();
|
|
490
|
-
const daysDiff = (now - postDate) / (1000 * 60 * 60 * 24);
|
|
491
|
-
|
|
492
|
-
if (this.filters.date === 'week' && daysDiff > 7) return false;
|
|
493
|
-
if (this.filters.date === 'month' && daysDiff > 30) return false;
|
|
494
|
-
if (this.filters.date === 'year' && daysDiff > 365) return false;
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
return true;
|
|
498
|
-
});
|
|
499
|
-
|
|
500
|
-
// Calculate relevance scores for text search
|
|
501
|
-
if (this.currentQuery) {
|
|
502
|
-
this.filteredResults = this.filteredResults.map(post => {
|
|
503
|
-
const query = this.currentQuery.toLowerCase();
|
|
504
|
-
let score = 0;
|
|
505
|
-
|
|
506
|
-
// Title match (highest weight)
|
|
507
|
-
if (post.title.toLowerCase().includes(query)) score += 10;
|
|
508
|
-
|
|
509
|
-
// Exact title match
|
|
510
|
-
if (post.title.toLowerCase() === query) score += 50;
|
|
511
|
-
|
|
512
|
-
// Tags match
|
|
513
|
-
if (post.tags.some(t => t.toLowerCase().includes(query))) score += 5;
|
|
514
|
-
|
|
515
|
-
// Categories match
|
|
516
|
-
if (post.categories.some(c => c.toLowerCase().includes(query))) score += 5;
|
|
517
|
-
|
|
518
|
-
// Content match (lower weight)
|
|
519
|
-
const contentMatches = (post.content.toLowerCase().match(new RegExp(query, 'g')) || []).length;
|
|
520
|
-
score += Math.min(contentMatches, 10);
|
|
521
|
-
|
|
522
|
-
return { ...post, relevanceScore: score };
|
|
523
|
-
});
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
this.sortResults();
|
|
527
|
-
this.currentPage = 1;
|
|
528
|
-
this.renderResults();
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
sortResults() {
|
|
532
|
-
switch (this.sortBy) {
|
|
533
|
-
case 'relevance':
|
|
534
|
-
if (this.currentQuery) {
|
|
535
|
-
this.filteredResults.sort((a, b) => (b.relevanceScore || 0) - (a.relevanceScore || 0));
|
|
536
|
-
}
|
|
537
|
-
break;
|
|
538
|
-
case 'date-desc':
|
|
539
|
-
this.filteredResults.sort((a, b) => new Date(b.date) - new Date(a.date));
|
|
540
|
-
break;
|
|
541
|
-
case 'date-asc':
|
|
542
|
-
this.filteredResults.sort((a, b) => new Date(a.date) - new Date(b.date));
|
|
543
|
-
break;
|
|
544
|
-
case 'title':
|
|
545
|
-
this.filteredResults.sort((a, b) => a.title.localeCompare(b.title));
|
|
546
|
-
break;
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
renderResults() {
|
|
551
|
-
if (this.filteredResults.length === 0) {
|
|
552
|
-
this.showNoResults();
|
|
553
|
-
return;
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
this.emptyState.hidden = true;
|
|
557
|
-
this.noResults.hidden = true;
|
|
558
|
-
this.resultsList.hidden = false;
|
|
559
|
-
|
|
560
|
-
// Update count
|
|
561
|
-
if (this.resultsCount) {
|
|
562
|
-
const countEl = this.resultsCount.querySelector('[data-count]');
|
|
563
|
-
if (countEl) countEl.textContent = this.filteredResults.length;
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
// Pagination
|
|
567
|
-
const start = (this.currentPage - 1) * this.resultsPerPage;
|
|
568
|
-
const end = start + this.resultsPerPage;
|
|
569
|
-
const pageResults = this.filteredResults.slice(start, end);
|
|
570
|
-
|
|
571
|
-
// Render results
|
|
572
|
-
this.resultsList.innerHTML = pageResults.map(post => this.renderResultItem(post)).join('');
|
|
573
|
-
|
|
574
|
-
// Render pagination
|
|
575
|
-
this.renderPagination();
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
renderResultItem(post) {
|
|
579
|
-
const excerpt = this.highlightMatches(post.excerpt);
|
|
580
|
-
const title = this.highlightMatches(post.title);
|
|
581
|
-
|
|
582
|
-
return `
|
|
583
|
-
<article class="search-result">
|
|
584
|
-
<h3 class="search-result__title">
|
|
585
|
-
<a href="${post.url}">${title}</a>
|
|
586
|
-
</h3>
|
|
587
|
-
<p class="search-result__excerpt">${excerpt}</p>
|
|
588
|
-
<div class="search-result__meta">
|
|
589
|
-
<time datetime="${post.date}">
|
|
590
|
-
${new Date(post.date).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' })}
|
|
591
|
-
</time>
|
|
592
|
-
<span>•</span>
|
|
593
|
-
<span>${post.readingTime} min read</span>
|
|
594
|
-
${post.difficulty ? `<span>•</span><span class="difficulty-badge difficulty-badge--${post.difficulty}">${post.difficulty}</span>` : ''}
|
|
595
|
-
</div>
|
|
596
|
-
</article>
|
|
597
|
-
`;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
highlightMatches(text) {
|
|
601
|
-
if (!this.currentQuery || !{{ highlight_matches }}) return text;
|
|
602
|
-
|
|
603
|
-
const query = this.currentQuery.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
604
|
-
const regex = new RegExp(`(${query})`, 'gi');
|
|
605
|
-
return text.replace(regex, '<mark>$1</mark>');
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
renderPagination() {
|
|
609
|
-
const totalPages = Math.ceil(this.filteredResults.length / this.resultsPerPage);
|
|
610
|
-
|
|
611
|
-
if (totalPages <= 1) {
|
|
612
|
-
this.pagination.hidden = true;
|
|
613
|
-
return;
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
this.pagination.hidden = false;
|
|
617
|
-
|
|
618
|
-
const buttons = [];
|
|
619
|
-
for (let i = 1; i <= totalPages; i++) {
|
|
620
|
-
const isActive = i === this.currentPage;
|
|
621
|
-
buttons.push(`
|
|
622
|
-
<button type="button"
|
|
623
|
-
class="pagination__button${isActive ? ' pagination__button--active' : ''}"
|
|
624
|
-
data-page="${i}"
|
|
625
|
-
${isActive ? 'aria-current="page"' : ''}>
|
|
626
|
-
${i}
|
|
627
|
-
</button>
|
|
628
|
-
`);
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
this.pagination.innerHTML = buttons.join('');
|
|
632
|
-
|
|
633
|
-
// Attach click handlers
|
|
634
|
-
this.pagination.querySelectorAll('[data-page]').forEach(btn => {
|
|
635
|
-
btn.addEventListener('click', (e) => {
|
|
636
|
-
this.currentPage = parseInt(e.target.dataset.page, 10);
|
|
637
|
-
this.renderResults();
|
|
638
|
-
this.resultsList.scrollIntoView({ behavior: 'smooth' });
|
|
639
|
-
});
|
|
640
|
-
});
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
showEmptyState() {
|
|
644
|
-
this.emptyState.hidden = false;
|
|
645
|
-
this.noResults.hidden = true;
|
|
646
|
-
this.resultsList.hidden = true;
|
|
647
|
-
this.pagination.hidden = true;
|
|
648
|
-
|
|
649
|
-
if (this.resultsCount) {
|
|
650
|
-
const countEl = this.resultsCount.querySelector('[data-count]');
|
|
651
|
-
if (countEl) countEl.textContent = '0';
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
showNoResults() {
|
|
656
|
-
this.emptyState.hidden = true;
|
|
657
|
-
this.noResults.hidden = false;
|
|
658
|
-
this.resultsList.hidden = true;
|
|
659
|
-
this.pagination.hidden = true;
|
|
660
|
-
|
|
661
|
-
if (this.resultsCount) {
|
|
662
|
-
const countEl = this.resultsCount.querySelector('[data-count]');
|
|
663
|
-
if (countEl) countEl.textContent = '0';
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
isFiltersEmpty() {
|
|
668
|
-
return this.filters.category.length === 0 &&
|
|
669
|
-
this.filters.tag.length === 0 &&
|
|
670
|
-
this.filters.difficulty.length === 0 &&
|
|
671
|
-
this.filters.date === 'all';
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
// Initialize
|
|
676
|
-
if (document.readyState === 'loading') {
|
|
677
|
-
document.addEventListener('DOMContentLoaded', () => new AdvancedSearch());
|
|
678
|
-
} else {
|
|
679
|
-
new AdvancedSearch();
|
|
680
|
-
}
|
|
681
|
-
})();
|
|
682
|
-
</script>
|