jekyll-theme-zer0 0.4.0 → 0.5.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 +16 -0
- data/README.md +6 -4
- data/_data/content_statistics.yml +401 -0
- data/_data/generate_statistics.rb +275 -0
- data/_data/navigation/home.yml +15 -0
- data/_data/navigation/main.yml +2 -5
- data/_includes/content/sitemap.html +935 -108
- data/_includes/navigation/navbar.html +6 -0
- data/_includes/stats/README.md +273 -0
- data/_includes/stats/stats-categories.html +146 -0
- data/_includes/stats/stats-header.html +123 -0
- data/_includes/stats/stats-metrics.html +243 -0
- data/_includes/stats/stats-no-data.html +180 -0
- data/_includes/stats/stats-overview.html +119 -0
- data/_includes/stats/stats-tags.html +142 -0
- data/_layouts/sitemap-collection.html +500 -0
- data/_layouts/stats.html +178 -0
- data/assets/css/stats.css +392 -0
- metadata +19 -6
|
@@ -1,123 +1,950 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
1
|
+
<!--
|
|
2
|
+
===================================================================
|
|
3
|
+
SITEMAP COMPONENT - Comprehensive Site Navigation Index
|
|
4
|
+
===================================================================
|
|
5
|
+
|
|
6
|
+
File: sitemap.html
|
|
7
|
+
Path: _includes/content/sitemap.html
|
|
8
|
+
Purpose: Interactive sitemap with advanced search, filtering, and sorting
|
|
9
|
+
|
|
10
|
+
Features:
|
|
11
|
+
- Real-time search across all content fields
|
|
12
|
+
- Multi-column sorting with visual indicators
|
|
13
|
+
- Collection-based filtering
|
|
14
|
+
- Responsive table design with Bootstrap styling
|
|
15
|
+
- URL query parameter support for direct search
|
|
16
|
+
- Statistics overview and content discovery tools
|
|
17
|
+
|
|
18
|
+
Dependencies:
|
|
19
|
+
- Bootstrap 5 for responsive table and styling
|
|
20
|
+
- Bootstrap Icons for visual elements
|
|
21
|
+
- Jekyll collections and site data
|
|
22
|
+
|
|
23
|
+
Enhanced Functionality:
|
|
24
|
+
- Advanced search with debouncing
|
|
25
|
+
- Collection filters and statistics
|
|
26
|
+
- Export capabilities (future enhancement)
|
|
27
|
+
- Keyboard navigation support
|
|
28
|
+
===================================================================
|
|
29
|
+
-->
|
|
30
|
+
|
|
31
|
+
<!-- ================================ -->
|
|
32
|
+
<!-- SITEMAP OVERVIEW AND STATS -->
|
|
33
|
+
<!-- ================================ -->
|
|
34
|
+
<div class="row mb-4">
|
|
35
|
+
<div class="col-12">
|
|
36
|
+
<div class="card border-0">
|
|
37
|
+
<div class="card-body">
|
|
38
|
+
<div class="row align-items-center">
|
|
39
|
+
<div class="col-md-8">
|
|
40
|
+
<h4 class="card-title mb-2">
|
|
41
|
+
<i class="bi bi-map text-primary"></i>
|
|
42
|
+
Site Navigation Index
|
|
43
|
+
</h4>
|
|
44
|
+
<p class="card-text text-muted mb-0">
|
|
45
|
+
Explore all <span id="totalPages" class="fw-bold text-info">0</span> pages across
|
|
46
|
+
<span id="totalCollections" class="fw-bold text-info">0</span> collections on this site.
|
|
47
|
+
</p>
|
|
48
|
+
</div>
|
|
49
|
+
<div class="col-md-4 text-md-end">
|
|
50
|
+
<div class="btn-group" role="group" aria-label="View options">
|
|
51
|
+
<button type="button" class="btn btn-outline-secondary" id="toggleStats">
|
|
52
|
+
<i class="bi bi-bar-chart"></i> Stats
|
|
53
|
+
</button>
|
|
54
|
+
<button type="button" class="btn btn-outline-secondary" id="resetFilters">
|
|
55
|
+
<i class="bi bi-arrow-clockwise"></i> Reset
|
|
56
|
+
</button>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<!-- ================================ -->
|
|
66
|
+
<!-- COLLECTION STATISTICS -->
|
|
67
|
+
<!-- ================================ -->
|
|
68
|
+
<div class="row mb-4 d-none" id="statsSection">
|
|
69
|
+
<div class="col-12">
|
|
70
|
+
<div class="card">
|
|
71
|
+
<div class="card-header">
|
|
72
|
+
<h5 class="mb-0">
|
|
73
|
+
<i class="bi bi-pie-chart"></i>
|
|
74
|
+
Content Statistics
|
|
75
|
+
</h5>
|
|
76
|
+
</div>
|
|
77
|
+
<div class="card-body">
|
|
78
|
+
<div class="row" id="collectionStats">
|
|
79
|
+
<!-- Dynamic collection statistics will be inserted here -->
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<!-- ================================ -->
|
|
87
|
+
<!-- SEARCH AND FILTER CONTROLS -->
|
|
88
|
+
<!-- ================================ -->
|
|
89
|
+
<div class="row mb-4">
|
|
90
|
+
<div class="col-12">
|
|
91
|
+
<div class="card">
|
|
92
|
+
<div class="card-body">
|
|
93
|
+
<div class="row g-3">
|
|
94
|
+
<!-- Search Input -->
|
|
95
|
+
<div class="col-md-6">
|
|
96
|
+
<label for="searchBar" class="form-label">
|
|
97
|
+
<i class="bi bi-search"></i> Search Content
|
|
98
|
+
</label>
|
|
99
|
+
<div class="input-group">
|
|
100
|
+
<input type="text"
|
|
101
|
+
class="form-control"
|
|
102
|
+
id="searchBar"
|
|
103
|
+
placeholder="Search titles, descriptions, tags, or any content..."
|
|
104
|
+
aria-describedby="searchHelp">
|
|
105
|
+
<button class="btn btn-outline-secondary"
|
|
106
|
+
type="button"
|
|
107
|
+
id="clearSearch"
|
|
108
|
+
title="Clear search"
|
|
109
|
+
aria-label="Clear search">
|
|
110
|
+
<i class="bi bi-x-lg"></i>
|
|
111
|
+
</button>
|
|
112
|
+
</div>
|
|
113
|
+
<div id="searchHelp" class="form-text">
|
|
114
|
+
Search across titles, descriptions, categories, tags, and more
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<!-- Collection Filter -->
|
|
119
|
+
<div class="col-md-3">
|
|
120
|
+
<label for="collectionFilter" class="form-label">
|
|
121
|
+
<i class="bi bi-funnel"></i> Filter by Collection
|
|
122
|
+
</label>
|
|
123
|
+
<select class="form-select" id="collectionFilter">
|
|
124
|
+
<option value="">All Collections</option>
|
|
125
|
+
<!-- Dynamic options will be added by JavaScript -->
|
|
126
|
+
</select>
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
<!-- Date Range Filter -->
|
|
130
|
+
<div class="col-md-3">
|
|
131
|
+
<label for="dateFilter" class="form-label">
|
|
132
|
+
<i class="bi bi-calendar-range"></i> Date Range
|
|
133
|
+
</label>
|
|
134
|
+
<select class="form-select" id="dateFilter">
|
|
135
|
+
<option value="">All Dates</option>
|
|
136
|
+
<option value="today">Today</option>
|
|
137
|
+
<option value="week">This Week</option>
|
|
138
|
+
<option value="month">This Month</option>
|
|
139
|
+
<option value="year">This Year</option>
|
|
140
|
+
</select>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
25
143
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
</table>
|
|
144
|
+
<!-- Results Counter -->
|
|
145
|
+
<div class="row mt-3">
|
|
146
|
+
<div class="col-12">
|
|
147
|
+
<div class="d-flex justify-content-between align-items-center">
|
|
148
|
+
<small class="text-muted">
|
|
149
|
+
Showing <span id="visibleCount" class="fw-bold">0</span> of
|
|
150
|
+
<span id="totalCount" class="fw-bold">0</span> pages
|
|
151
|
+
</small>
|
|
152
|
+
<div class="btn-group btn-group-sm" role="group" aria-label="Display options">
|
|
153
|
+
<input type="radio" class="btn-check" name="viewMode" id="tableView" checked>
|
|
154
|
+
<label class="btn btn-outline-primary" for="tableView">
|
|
155
|
+
<i class="bi bi-table"></i> Table
|
|
156
|
+
</label>
|
|
157
|
+
<input type="radio" class="btn-check" name="viewMode" id="cardView">
|
|
158
|
+
<label class="btn btn-outline-primary" for="cardView">
|
|
159
|
+
<i class="bi bi-grid"></i> Cards
|
|
160
|
+
</label>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
51
168
|
</div>
|
|
52
169
|
|
|
53
|
-
<!--
|
|
170
|
+
<!-- ================================ -->
|
|
171
|
+
<!-- RESPONSIVE DATA TABLE -->
|
|
172
|
+
<!-- ================================ -->
|
|
173
|
+
<div class="row" id="tableViewContainer">
|
|
174
|
+
<div class="col-12">
|
|
175
|
+
<div class="card">
|
|
176
|
+
<div class="card-body p-0">
|
|
177
|
+
<div class="table-responsive">
|
|
178
|
+
<table class="table table-hover mb-0" id="sitemapTable">
|
|
179
|
+
<thead class="table-dark sticky-top">
|
|
180
|
+
<tr>
|
|
181
|
+
<th scope="col" data-type="string" data-order="asc" class="sitemap-col-collection">
|
|
182
|
+
Collection
|
|
183
|
+
<i class="sort-icon bi bi-arrow-down-up ms-1"></i>
|
|
184
|
+
</th>
|
|
185
|
+
<th scope="col" data-type="string" data-order="asc" class="sitemap-col-title">
|
|
186
|
+
Page Title
|
|
187
|
+
<i class="sort-icon bi bi-arrow-down-up ms-1"></i>
|
|
188
|
+
</th>
|
|
189
|
+
<th scope="col" data-type="string" data-order="asc" class="d-none d-md-table-cell">
|
|
190
|
+
Description
|
|
191
|
+
<i class="sort-icon bi bi-arrow-down-up ms-1"></i>
|
|
192
|
+
</th>
|
|
193
|
+
<th scope="col" data-type="date" data-order="desc" class="sitemap-col-date">
|
|
194
|
+
Last Modified
|
|
195
|
+
<i class="sort-icon bi bi-arrow-down-up ms-1"></i>
|
|
196
|
+
</th>
|
|
197
|
+
<th scope="col" data-type="string" data-order="asc" class="d-none d-lg-table-cell">
|
|
198
|
+
Categories
|
|
199
|
+
<i class="sort-icon bi bi-arrow-down-up ms-1"></i>
|
|
200
|
+
</th>
|
|
201
|
+
<th scope="col" data-type="string" data-order="asc" class="d-none d-xl-table-cell">
|
|
202
|
+
Tags
|
|
203
|
+
<i class="sort-icon bi bi-arrow-down-up ms-1"></i>
|
|
204
|
+
</th>
|
|
205
|
+
<th scope="col" class="text-center sitemap-col-actions">
|
|
206
|
+
Actions
|
|
207
|
+
</th>
|
|
208
|
+
</tr>
|
|
209
|
+
</thead>
|
|
210
|
+
<tbody id="sitemapTableBody">
|
|
211
|
+
{% assign total_pages = 0 %}
|
|
212
|
+
{% assign collections_data = '' %}
|
|
213
|
+
|
|
214
|
+
{% comment %} Include regular pages {% endcomment %}
|
|
215
|
+
{% for page in site.pages %}
|
|
216
|
+
{% unless page.sitemap == false %}
|
|
217
|
+
{% assign total_pages = total_pages | plus: 1 %}
|
|
218
|
+
<tr data-collection="pages"
|
|
219
|
+
data-title="{{ page.title | default: page.name | escape }}"
|
|
220
|
+
data-description="{{ page.description | escape }}"
|
|
221
|
+
data-categories="{{ page.categories | join: ', ' | escape }}"
|
|
222
|
+
data-tags="{{ page.tags | join: ', ' | escape }}"
|
|
223
|
+
data-date="{{ page.lastmod | default: page.date | date: '%Y-%m-%d' }}"
|
|
224
|
+
data-url="{{ page.url | relative_url }}">
|
|
225
|
+
|
|
226
|
+
<td>
|
|
227
|
+
<span class="badge bg-primary rounded-pill">Pages</span>
|
|
228
|
+
</td>
|
|
229
|
+
<td>
|
|
230
|
+
<a href="{{ page.url | relative_url }}"
|
|
231
|
+
class="text-decoration-none fw-medium">
|
|
232
|
+
{{ page.title | default: page.name | truncate: 50 }}
|
|
233
|
+
</a>
|
|
234
|
+
{% if page.description %}
|
|
235
|
+
<br><small class="text-muted d-md-none">{{ page.description | truncate: 80 }}</small>
|
|
236
|
+
{% endif %}
|
|
237
|
+
</td>
|
|
238
|
+
<td class="d-none d-md-table-cell">
|
|
239
|
+
<small class="text-muted">
|
|
240
|
+
{{ page.description | default: "No description available" | truncate: 120 }}
|
|
241
|
+
</small>
|
|
242
|
+
</td>
|
|
243
|
+
<td>
|
|
244
|
+
<small class="text-muted">
|
|
245
|
+
{{ page.lastmod | default: page.date | date: "%b %d, %Y" | default: "Unknown" }}
|
|
246
|
+
</small>
|
|
247
|
+
</td>
|
|
248
|
+
<td class="d-none d-lg-table-cell">
|
|
249
|
+
{% if page.categories and page.categories.size > 0 %}
|
|
250
|
+
{% for category in page.categories limit: 2 %}
|
|
251
|
+
<span class="badge bg-secondary me-1">{{ category }}</span>
|
|
252
|
+
{% endfor %}
|
|
253
|
+
{% if page.categories.size > 2 %}
|
|
254
|
+
<span class="badge bg-light text-dark">+{{ page.categories.size | minus: 2 }}</span>
|
|
255
|
+
{% endif %}
|
|
256
|
+
{% endif %}
|
|
257
|
+
</td>
|
|
258
|
+
<td class="d-none d-xl-table-cell">
|
|
259
|
+
{% if page.tags and page.tags.size > 0 %}
|
|
260
|
+
{% for tag in page.tags limit: 3 %}
|
|
261
|
+
<span class="badge bg-outline-primary me-1">#{{ tag }}</span>
|
|
262
|
+
{% endfor %}
|
|
263
|
+
{% if page.tags.size > 3 %}
|
|
264
|
+
<span class="badge bg-light text-dark">+{{ page.tags.size | minus: 3 }}</span>
|
|
265
|
+
{% endif %}
|
|
266
|
+
{% endif %}
|
|
267
|
+
</td>
|
|
268
|
+
<td class="text-center">
|
|
269
|
+
<div class="btn-group btn-group-sm" role="group">
|
|
270
|
+
<a href="{{ page.url | relative_url }}"
|
|
271
|
+
class="btn btn-outline-primary btn-sm"
|
|
272
|
+
title="View page">
|
|
273
|
+
<i class="bi bi-eye"></i>
|
|
274
|
+
</a>
|
|
275
|
+
<button class="btn btn-outline-secondary btn-sm copy-url"
|
|
276
|
+
data-url="{{ site.url }}{{ page.url }}"
|
|
277
|
+
title="Copy URL">
|
|
278
|
+
<i class="bi bi-clipboard"></i>
|
|
279
|
+
</button>
|
|
280
|
+
</div>
|
|
281
|
+
</td>
|
|
282
|
+
</tr>
|
|
283
|
+
{% endunless %}
|
|
284
|
+
{% endfor %}
|
|
285
|
+
|
|
286
|
+
{% comment %} Include collection documents {% endcomment %}
|
|
287
|
+
{% for collection in site.collections %}
|
|
288
|
+
{% for item in collection.docs %}
|
|
289
|
+
{% unless item.sitemap == false %}
|
|
290
|
+
{% assign total_pages = total_pages | plus: 1 %}
|
|
291
|
+
<tr data-collection="{{ collection.label }}"
|
|
292
|
+
data-title="{{ item.title | escape }}"
|
|
293
|
+
data-description="{{ item.description | escape }}"
|
|
294
|
+
data-categories="{{ item.categories | join: ', ' | escape }}"
|
|
295
|
+
data-tags="{{ item.tags | join: ', ' | escape }}"
|
|
296
|
+
data-date="{{ item.lastmod | default: item.date | date: '%Y-%m-%d' }}"
|
|
297
|
+
data-url="{{ item.url | relative_url }}">
|
|
298
|
+
|
|
299
|
+
<td>
|
|
300
|
+
<span class="badge bg-info rounded-pill">{{ collection.label | capitalize }}</span>
|
|
301
|
+
</td>
|
|
302
|
+
<td>
|
|
303
|
+
<a href="{{ item.url | relative_url }}"
|
|
304
|
+
class="text-decoration-none fw-medium">
|
|
305
|
+
{{ item.title | truncate: 50 }}
|
|
306
|
+
</a>
|
|
307
|
+
{% if item.description %}
|
|
308
|
+
<br><small class="text-muted d-md-none">{{ item.description | truncate: 80 }}</small>
|
|
309
|
+
{% endif %}
|
|
310
|
+
</td>
|
|
311
|
+
<td class="d-none d-md-table-cell">
|
|
312
|
+
<small class="text-muted">
|
|
313
|
+
{{ item.description | default: item.excerpt | strip_html | default: "No description available" | truncate: 120 }}
|
|
314
|
+
</small>
|
|
315
|
+
</td>
|
|
316
|
+
<td>
|
|
317
|
+
<small class="text-muted">
|
|
318
|
+
{{ item.lastmod | default: item.date | date: "%b %d, %Y" | default: "Unknown" }}
|
|
319
|
+
</small>
|
|
320
|
+
</td>
|
|
321
|
+
<td class="d-none d-lg-table-cell">
|
|
322
|
+
{% if item.categories and item.categories.size > 0 %}
|
|
323
|
+
{% for category in item.categories limit: 2 %}
|
|
324
|
+
<span class="badge bg-secondary me-1">{{ category }}</span>
|
|
325
|
+
{% endfor %}
|
|
326
|
+
{% if item.categories.size > 2 %}
|
|
327
|
+
<span class="badge bg-light text-dark">+{{ item.categories.size | minus: 2 }}</span>
|
|
328
|
+
{% endif %}
|
|
329
|
+
{% endif %}
|
|
330
|
+
</td>
|
|
331
|
+
<td class="d-none d-xl-table-cell">
|
|
332
|
+
{% if item.tags and item.tags.size > 0 %}
|
|
333
|
+
{% for tag in item.tags limit: 3 %}
|
|
334
|
+
<span class="badge bg-outline-primary me-1">#{{ tag }}</span>
|
|
335
|
+
{% endfor %}
|
|
336
|
+
{% if item.tags.size > 3 %}
|
|
337
|
+
<span class="badge bg-light text-dark">+{{ item.tags.size | minus: 3 }}</span>
|
|
338
|
+
{% endif %}
|
|
339
|
+
{% endif %}
|
|
340
|
+
</td>
|
|
341
|
+
<td class="text-center">
|
|
342
|
+
<div class="btn-group btn-group-sm" role="group">
|
|
343
|
+
<a href="{{ item.url | relative_url }}"
|
|
344
|
+
class="btn btn-outline-primary btn-sm"
|
|
345
|
+
title="View page">
|
|
346
|
+
<i class="bi bi-eye"></i>
|
|
347
|
+
</a>
|
|
348
|
+
<button class="btn btn-outline-secondary btn-sm copy-url"
|
|
349
|
+
data-url="{{ site.url }}{{ item.url }}"
|
|
350
|
+
title="Copy URL">
|
|
351
|
+
<i class="bi bi-clipboard"></i>
|
|
352
|
+
</button>
|
|
353
|
+
</div>
|
|
354
|
+
</td>
|
|
355
|
+
</tr>
|
|
356
|
+
{% endunless %}
|
|
357
|
+
{% endfor %}
|
|
358
|
+
{% endfor %}
|
|
359
|
+
</tbody>
|
|
360
|
+
</table>
|
|
361
|
+
</div>
|
|
362
|
+
</div>
|
|
363
|
+
</div>
|
|
364
|
+
</div>
|
|
365
|
+
</div>
|
|
54
366
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const sortedRows = rows.sort((a, b) => {
|
|
66
|
-
const aValue = a.children[headerIndex].textContent;
|
|
67
|
-
const bValue = b.children[headerIndex].textContent;
|
|
68
|
-
if (type === 'date') {
|
|
69
|
-
const aDate = aValue === "null" ? new Date(0) : new Date(...aValue.split('/').map((val, idx) => parseInt(val) - (idx === 1 ? 1 : 0))); // subtract 1 from month
|
|
70
|
-
const bDate = bValue === "null" ? new Date(0) : new Date(...bValue.split('/').map((val, idx) => parseInt(val) - (idx === 1 ? 1 : 0))); // subtract 1 from month
|
|
71
|
-
return order === 'asc' ? aDate - bDate : bDate - aDate;
|
|
72
|
-
}
|
|
73
|
-
return order === 'asc' ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue);
|
|
74
|
-
});
|
|
75
|
-
sortedRows.forEach(row => newTbody.appendChild(row));
|
|
76
|
-
tableElement.replaceChild(newTbody, currentTbody);
|
|
77
|
-
header.setAttribute('data-order', order === 'asc' ? 'desc' : 'asc');
|
|
78
|
-
document.querySelectorAll('.sort-icon').forEach(icon => icon.className = 'sort-icon'); // clear all icons
|
|
79
|
-
header.querySelector('.sort-icon').className = order === 'asc' ? 'sort-icon bi bi-arrow-down' : 'sort-icon bi bi-arrow-up'; // set the icon for the sorted column
|
|
367
|
+
<!-- ================================ -->
|
|
368
|
+
<!-- CARDS VIEW CONTAINER -->
|
|
369
|
+
<!-- ================================ -->
|
|
370
|
+
<div class="row d-none" id="cardViewContainer">
|
|
371
|
+
<div class="col-12">
|
|
372
|
+
<div id="sitemapCardsContainer" class="row row-cols-1 row-cols-md-2 row-cols-lg-3 row-cols-xl-4 g-4">
|
|
373
|
+
<!-- Cards will be dynamically generated here -->
|
|
374
|
+
</div>
|
|
375
|
+
</div>
|
|
376
|
+
</div>
|
|
80
377
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
378
|
+
<!-- ================================ -->
|
|
379
|
+
<!-- ENHANCED SITEMAP FUNCTIONALITY -->
|
|
380
|
+
<!-- ================================ -->
|
|
381
|
+
<style>
|
|
382
|
+
/* Custom Sitemap Styles */
|
|
383
|
+
.sitemap-col-collection { min-width: 120px; }
|
|
384
|
+
.sitemap-col-title { min-width: 200px; }
|
|
385
|
+
.sitemap-col-date { min-width: 120px; }
|
|
386
|
+
.sitemap-col-actions { width: 80px; }
|
|
387
|
+
|
|
388
|
+
.sort-icon {
|
|
389
|
+
opacity: 0.5;
|
|
390
|
+
transition: opacity 0.2s ease;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
th:hover .sort-icon {
|
|
394
|
+
opacity: 1;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.sort-icon.active {
|
|
398
|
+
opacity: 1;
|
|
399
|
+
color: #0d6efd;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
#sitemapTable tbody tr {
|
|
403
|
+
transition: background-color 0.15s ease;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.badge.bg-outline-primary {
|
|
407
|
+
color: #0d6efd;
|
|
408
|
+
border: 1px solid #0d6efd;
|
|
409
|
+
background-color: transparent;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/* Cards view styles */
|
|
413
|
+
.sitemap-card {
|
|
414
|
+
transition: transform 0.2s ease;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.sitemap-card:hover {
|
|
418
|
+
transform: translateY(-2px);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.sitemap-card .card {
|
|
422
|
+
border: 1px solid var(--bs-border-color);
|
|
423
|
+
transition: box-shadow 0.15s ease-in-out;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.sitemap-card .card:hover {
|
|
427
|
+
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
.sitemap-card .card-title a {
|
|
431
|
+
color: inherit;
|
|
432
|
+
transition: color 0.15s ease;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
.sitemap-card .card-title a:hover {
|
|
436
|
+
color: var(--bs-primary);
|
|
437
|
+
}
|
|
84
438
|
|
|
85
|
-
|
|
439
|
+
/* Responsive adjustments */
|
|
440
|
+
@media (max-width: 768px) {
|
|
441
|
+
.sitemap-col-collection,
|
|
442
|
+
.sitemap-col-title,
|
|
443
|
+
.sitemap-col-date {
|
|
444
|
+
min-width: auto;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
.sitemap-card .card-body {
|
|
448
|
+
padding: 0.75rem;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
.sitemap-card .card-title {
|
|
452
|
+
font-size: 0.9rem;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/* Dark mode improvements */
|
|
457
|
+
@media (prefers-color-scheme: dark) {
|
|
458
|
+
.sitemap-card .card {
|
|
459
|
+
border-color: var(--bs-border-color-translucent);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
.badge.bg-light {
|
|
463
|
+
background-color: var(--bs-gray-600) !important;
|
|
464
|
+
color: var(--bs-gray-100) !important;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
</style>
|
|
86
468
|
|
|
87
469
|
<script>
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
470
|
+
/**
|
|
471
|
+
* Enhanced Sitemap Functionality
|
|
472
|
+
* Features: Advanced search, sorting, filtering, statistics, and card/table views
|
|
473
|
+
*/
|
|
474
|
+
class SitemapManager {
|
|
475
|
+
constructor() {
|
|
476
|
+
this.searchInput = document.getElementById('searchBar');
|
|
477
|
+
this.collectionFilter = document.getElementById('collectionFilter');
|
|
478
|
+
this.dateFilter = document.getElementById('dateFilter');
|
|
479
|
+
this.clearSearchBtn = document.getElementById('clearSearch');
|
|
480
|
+
this.resetFiltersBtn = document.getElementById('resetFilters');
|
|
481
|
+
this.toggleStatsBtn = document.getElementById('toggleStats');
|
|
482
|
+
this.statsSection = document.getElementById('statsSection');
|
|
483
|
+
this.table = document.getElementById('sitemapTable');
|
|
484
|
+
this.tbody = document.getElementById('sitemapTableBody');
|
|
485
|
+
this.tableViewContainer = document.getElementById('tableViewContainer');
|
|
486
|
+
this.cardViewContainer = document.getElementById('cardViewContainer');
|
|
487
|
+
this.cardsContainer = document.getElementById('sitemapCardsContainer');
|
|
488
|
+
this.tableViewBtn = document.getElementById('tableView');
|
|
489
|
+
this.cardViewBtn = document.getElementById('cardView');
|
|
490
|
+
|
|
491
|
+
this.rows = Array.from(this.tbody.querySelectorAll('tr'));
|
|
492
|
+
this.visibleCount = document.getElementById('visibleCount');
|
|
493
|
+
this.totalCount = document.getElementById('totalCount');
|
|
494
|
+
|
|
495
|
+
this.searchTimeout = null;
|
|
496
|
+
this.collections = new Set();
|
|
497
|
+
this.currentView = 'table';
|
|
498
|
+
|
|
499
|
+
this.init();
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
init() {
|
|
503
|
+
this.setupCollectionFilter();
|
|
504
|
+
this.updateCounts();
|
|
505
|
+
this.updateStatistics();
|
|
506
|
+
this.generateCards();
|
|
507
|
+
this.bindEvents();
|
|
508
|
+
this.handleUrlParams();
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
setupCollectionFilter() {
|
|
512
|
+
// Collect unique collections
|
|
513
|
+
this.rows.forEach(row => {
|
|
514
|
+
const collection = row.dataset.collection;
|
|
515
|
+
if (collection) {
|
|
516
|
+
this.collections.add(collection);
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
// Populate collection filter
|
|
521
|
+
Array.from(this.collections).sort().forEach(collection => {
|
|
522
|
+
const option = document.createElement('option');
|
|
523
|
+
option.value = collection;
|
|
524
|
+
option.textContent = collection.charAt(0).toUpperCase() + collection.slice(1);
|
|
525
|
+
this.collectionFilter.appendChild(option);
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
generateCards() {
|
|
530
|
+
this.cardsContainer.innerHTML = '';
|
|
531
|
+
|
|
532
|
+
this.rows.forEach(row => {
|
|
533
|
+
const collection = row.dataset.collection;
|
|
534
|
+
const title = row.dataset.title;
|
|
535
|
+
const description = row.dataset.description || 'No description available';
|
|
536
|
+
const categories = row.dataset.categories;
|
|
537
|
+
const tags = row.dataset.tags;
|
|
538
|
+
const date = row.dataset.date;
|
|
539
|
+
const url = row.dataset.url;
|
|
540
|
+
|
|
541
|
+
const formattedDate = date ? new Date(date).toLocaleDateString('en-US', {
|
|
542
|
+
year: 'numeric',
|
|
543
|
+
month: 'short',
|
|
544
|
+
day: 'numeric'
|
|
545
|
+
}) : 'Unknown';
|
|
546
|
+
|
|
547
|
+
const badgeClass = collection === 'pages' ? 'bg-primary' : 'bg-info';
|
|
548
|
+
|
|
549
|
+
const cardHtml = `
|
|
550
|
+
<div class="col sitemap-card"
|
|
551
|
+
data-collection="${collection}"
|
|
552
|
+
data-title="${title}"
|
|
553
|
+
data-description="${description}"
|
|
554
|
+
data-categories="${categories}"
|
|
555
|
+
data-tags="${tags}"
|
|
556
|
+
data-date="${date}">
|
|
557
|
+
<div class="card h-100 shadow-sm">
|
|
558
|
+
<div class="card-header d-flex justify-content-between align-items-center">
|
|
559
|
+
<span class="badge ${badgeClass} rounded-pill">${collection.charAt(0).toUpperCase() + collection.slice(1)}</span>
|
|
560
|
+
<small class="text-muted">${formattedDate}</small>
|
|
561
|
+
</div>
|
|
562
|
+
<div class="card-body">
|
|
563
|
+
<h6 class="card-title">
|
|
564
|
+
<a href="${url}" class="text-decoration-none">${title}</a>
|
|
565
|
+
</h6>
|
|
566
|
+
<p class="card-text text-muted small">${description.length > 100 ? description.substring(0, 100) + '...' : description}</p>
|
|
567
|
+
</div>
|
|
568
|
+
<div class="card-footer bg-transparent">
|
|
569
|
+
<div class="d-flex justify-content-between align-items-center">
|
|
570
|
+
<div class="btn-group btn-group-sm" role="group">
|
|
571
|
+
<a href="${url}" class="btn btn-outline-primary btn-sm" title="View page">
|
|
572
|
+
<i class="bi bi-eye"></i>
|
|
573
|
+
</a>
|
|
574
|
+
<button class="btn btn-outline-secondary btn-sm copy-url"
|
|
575
|
+
data-url="${location.origin}${url}" title="Copy URL">
|
|
576
|
+
<i class="bi bi-clipboard"></i>
|
|
577
|
+
</button>
|
|
578
|
+
</div>
|
|
579
|
+
${categories ? `<small class="text-muted">${categories.split(',').slice(0,2).join(', ')}</small>` : ''}
|
|
580
|
+
</div>
|
|
581
|
+
</div>
|
|
582
|
+
</div>
|
|
583
|
+
</div>
|
|
584
|
+
`;
|
|
585
|
+
|
|
586
|
+
this.cardsContainer.insertAdjacentHTML('beforeend', cardHtml);
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
// Re-attach copy URL event listeners for cards
|
|
590
|
+
this.cardsContainer.querySelectorAll('.copy-url').forEach(btn => {
|
|
591
|
+
btn.addEventListener('click', (e) => this.copyUrl(e));
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
bindEvents() {
|
|
596
|
+
// Search with debouncing
|
|
597
|
+
this.searchInput.addEventListener('input', () => {
|
|
598
|
+
clearTimeout(this.searchTimeout);
|
|
599
|
+
this.searchTimeout = setTimeout(() => this.performSearch(), 300);
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
this.clearSearchBtn.addEventListener('click', () => {
|
|
603
|
+
this.searchInput.value = '';
|
|
604
|
+
this.performSearch();
|
|
605
|
+
this.searchInput.focus();
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
// Filters
|
|
609
|
+
this.collectionFilter.addEventListener('change', () => this.applyFilters());
|
|
610
|
+
this.dateFilter.addEventListener('change', () => this.applyFilters());
|
|
611
|
+
|
|
612
|
+
// Reset filters
|
|
613
|
+
this.resetFiltersBtn.addEventListener('click', () => this.resetFilters());
|
|
614
|
+
|
|
615
|
+
// Toggle statistics
|
|
616
|
+
this.toggleStatsBtn.addEventListener('click', () => this.toggleStatistics());
|
|
617
|
+
|
|
618
|
+
// View mode toggle
|
|
619
|
+
this.tableViewBtn.addEventListener('change', () => {
|
|
620
|
+
if (this.tableViewBtn.checked) {
|
|
621
|
+
this.switchToTableView();
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
this.cardViewBtn.addEventListener('change', () => {
|
|
626
|
+
if (this.cardViewBtn.checked) {
|
|
627
|
+
this.switchToCardView();
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
// Column sorting (table view only)
|
|
632
|
+
this.table.querySelectorAll('th[data-type]').forEach(header => {
|
|
633
|
+
header.addEventListener('click', () => this.sortTable(header));
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
// Copy URL functionality
|
|
637
|
+
document.querySelectorAll('.copy-url').forEach(btn => {
|
|
638
|
+
btn.addEventListener('click', (e) => this.copyUrl(e));
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
// Keyboard shortcuts
|
|
642
|
+
document.addEventListener('keydown', (e) => this.handleKeyboardShortcuts(e));
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
switchToTableView() {
|
|
646
|
+
this.currentView = 'table';
|
|
647
|
+
this.tableViewContainer.classList.remove('d-none');
|
|
648
|
+
this.cardViewContainer.classList.add('d-none');
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
switchToCardView() {
|
|
652
|
+
this.currentView = 'card';
|
|
653
|
+
this.tableViewContainer.classList.add('d-none');
|
|
654
|
+
this.cardViewContainer.classList.remove('d-none');
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
performSearch() {
|
|
658
|
+
const searchTerm = this.searchInput.value.toLowerCase().trim();
|
|
659
|
+
|
|
660
|
+
if (this.currentView === 'table') {
|
|
661
|
+
this.rows.forEach(row => {
|
|
662
|
+
if (!searchTerm) {
|
|
663
|
+
row.style.display = '';
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
const searchableText = [
|
|
668
|
+
row.dataset.title,
|
|
669
|
+
row.dataset.description,
|
|
670
|
+
row.dataset.categories,
|
|
671
|
+
row.dataset.tags,
|
|
672
|
+
row.dataset.collection
|
|
673
|
+
].join(' ').toLowerCase();
|
|
674
|
+
|
|
675
|
+
row.style.display = searchableText.includes(searchTerm) ? '' : 'none';
|
|
676
|
+
});
|
|
677
|
+
} else {
|
|
678
|
+
// Card view search
|
|
679
|
+
const cards = this.cardsContainer.querySelectorAll('.sitemap-card');
|
|
680
|
+
cards.forEach(card => {
|
|
681
|
+
if (!searchTerm) {
|
|
682
|
+
card.style.display = '';
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const searchableText = [
|
|
687
|
+
card.dataset.title,
|
|
688
|
+
card.dataset.description,
|
|
689
|
+
card.dataset.categories,
|
|
690
|
+
card.dataset.tags,
|
|
691
|
+
card.dataset.collection
|
|
692
|
+
].join(' ').toLowerCase();
|
|
693
|
+
|
|
694
|
+
card.style.display = searchableText.includes(searchTerm) ? '' : 'none';
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
this.applyFilters();
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
applyFilters() {
|
|
702
|
+
const collectionFilter = this.collectionFilter.value;
|
|
703
|
+
const dateFilter = this.dateFilter.value;
|
|
704
|
+
|
|
705
|
+
if (this.currentView === 'table') {
|
|
706
|
+
this.rows.forEach(row => {
|
|
707
|
+
let show = true;
|
|
708
|
+
|
|
709
|
+
// Skip if already hidden by search
|
|
710
|
+
if (row.style.display === 'none') return;
|
|
711
|
+
|
|
712
|
+
// Collection filter
|
|
713
|
+
if (collectionFilter && row.dataset.collection !== collectionFilter) {
|
|
714
|
+
show = false;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Date filter
|
|
718
|
+
if (dateFilter && show) {
|
|
719
|
+
const rowDate = new Date(row.dataset.date);
|
|
720
|
+
const now = new Date();
|
|
721
|
+
|
|
722
|
+
switch (dateFilter) {
|
|
723
|
+
case 'today':
|
|
724
|
+
show = this.isSameDay(rowDate, now);
|
|
725
|
+
break;
|
|
726
|
+
case 'week':
|
|
727
|
+
show = this.isWithinDays(rowDate, 7);
|
|
728
|
+
break;
|
|
729
|
+
case 'month':
|
|
730
|
+
show = this.isWithinDays(rowDate, 30);
|
|
731
|
+
break;
|
|
732
|
+
case 'year':
|
|
733
|
+
show = this.isWithinDays(rowDate, 365);
|
|
734
|
+
break;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
row.style.display = show ? '' : 'none';
|
|
739
|
+
});
|
|
740
|
+
} else {
|
|
741
|
+
// Card view filtering
|
|
742
|
+
const cards = this.cardsContainer.querySelectorAll('.sitemap-card');
|
|
743
|
+
cards.forEach(card => {
|
|
744
|
+
let show = true;
|
|
745
|
+
|
|
746
|
+
// Skip if already hidden by search
|
|
747
|
+
if (card.style.display === 'none') return;
|
|
748
|
+
|
|
749
|
+
// Collection filter
|
|
750
|
+
if (collectionFilter && card.dataset.collection !== collectionFilter) {
|
|
751
|
+
show = false;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// Date filter
|
|
755
|
+
if (dateFilter && show) {
|
|
756
|
+
const cardDate = new Date(card.dataset.date);
|
|
757
|
+
const now = new Date();
|
|
758
|
+
|
|
759
|
+
switch (dateFilter) {
|
|
760
|
+
case 'today':
|
|
761
|
+
show = this.isSameDay(cardDate, now);
|
|
762
|
+
break;
|
|
763
|
+
case 'week':
|
|
764
|
+
show = this.isWithinDays(cardDate, 7);
|
|
765
|
+
break;
|
|
766
|
+
case 'month':
|
|
767
|
+
show = this.isWithinDays(cardDate, 30);
|
|
768
|
+
break;
|
|
769
|
+
case 'year':
|
|
770
|
+
show = this.isWithinDays(cardDate, 365);
|
|
771
|
+
break;
|
|
772
|
+
}
|
|
105
773
|
}
|
|
774
|
+
|
|
775
|
+
card.style.display = show ? '' : 'none';
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
this.updateCounts();
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
sortTable(header) {
|
|
783
|
+
const headerIndex = Array.from(header.parentNode.children).indexOf(header);
|
|
784
|
+
const type = header.getAttribute('data-type');
|
|
785
|
+
const currentOrder = header.getAttribute('data-order');
|
|
786
|
+
const newOrder = currentOrder === 'asc' ? 'desc' : 'asc';
|
|
787
|
+
|
|
788
|
+
// Clear all sort icons
|
|
789
|
+
this.table.querySelectorAll('.sort-icon').forEach(icon => {
|
|
790
|
+
icon.className = 'sort-icon bi bi-arrow-down-up ms-1';
|
|
791
|
+
icon.classList.remove('active');
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
// Update current header
|
|
795
|
+
header.setAttribute('data-order', newOrder);
|
|
796
|
+
const icon = header.querySelector('.sort-icon');
|
|
797
|
+
icon.className = `sort-icon bi bi-arrow-${newOrder === 'asc' ? 'up' : 'down'} ms-1 active`;
|
|
798
|
+
|
|
799
|
+
// Sort rows
|
|
800
|
+
const sortedRows = this.rows.sort((a, b) => {
|
|
801
|
+
let aValue = a.children[headerIndex].textContent.trim();
|
|
802
|
+
let bValue = b.children[headerIndex].textContent.trim();
|
|
803
|
+
|
|
804
|
+
if (type === 'date') {
|
|
805
|
+
aValue = aValue === "Unknown" ? new Date(0) : new Date(aValue);
|
|
806
|
+
bValue = bValue === "Unknown" ? new Date(0) : new Date(bValue);
|
|
807
|
+
return newOrder === 'asc' ? aValue - bValue : bValue - aValue;
|
|
106
808
|
}
|
|
809
|
+
|
|
810
|
+
return newOrder === 'asc' ?
|
|
811
|
+
aValue.localeCompare(bValue) :
|
|
812
|
+
bValue.localeCompare(aValue);
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
// Reorder DOM
|
|
816
|
+
sortedRows.forEach(row => this.tbody.appendChild(row));
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
updateCounts() {
|
|
820
|
+
let visibleCount = 0;
|
|
821
|
+
|
|
822
|
+
if (this.currentView === 'table') {
|
|
823
|
+
visibleCount = this.rows.filter(row => row.style.display !== 'none').length;
|
|
824
|
+
} else {
|
|
825
|
+
const cards = this.cardsContainer.querySelectorAll('.sitemap-card');
|
|
826
|
+
visibleCount = Array.from(cards).filter(card => card.style.display !== 'none').length;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
this.visibleCount.textContent = visibleCount;
|
|
830
|
+
this.totalCount.textContent = this.rows.length;
|
|
831
|
+
|
|
832
|
+
// Update page statistics
|
|
833
|
+
document.getElementById('totalPages').textContent = this.rows.length;
|
|
834
|
+
document.getElementById('totalCollections').textContent = this.collections.size;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
updateStatistics() {
|
|
838
|
+
const statsContainer = document.getElementById('collectionStats');
|
|
839
|
+
const collectionCounts = {};
|
|
840
|
+
|
|
841
|
+
this.rows.forEach(row => {
|
|
842
|
+
const collection = row.dataset.collection;
|
|
843
|
+
collectionCounts[collection] = (collectionCounts[collection] || 0) + 1;
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
const statsHtml = Object.entries(collectionCounts)
|
|
847
|
+
.sort(([,a], [,b]) => b - a)
|
|
848
|
+
.map(([collection, count]) => `
|
|
849
|
+
<div class="col-md-6 col-lg-4 mb-3">
|
|
850
|
+
<div class="card text-center h-100">
|
|
851
|
+
<div class="card-body">
|
|
852
|
+
<h5 class="card-title">${collection.charAt(0).toUpperCase() + collection.slice(1)}</h5>
|
|
853
|
+
<p class="card-text display-6 text-primary">${count}</p>
|
|
854
|
+
<small class="text-muted">${((count / this.rows.length) * 100).toFixed(1)}% of total</small>
|
|
855
|
+
</div>
|
|
856
|
+
</div>
|
|
857
|
+
</div>
|
|
858
|
+
`).join('');
|
|
859
|
+
|
|
860
|
+
statsContainer.innerHTML = statsHtml;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
toggleStatistics() {
|
|
864
|
+
this.statsSection.classList.toggle('d-none');
|
|
865
|
+
const isVisible = !this.statsSection.classList.contains('d-none');
|
|
866
|
+
this.toggleStatsBtn.innerHTML = `
|
|
867
|
+
<i class="bi bi-${isVisible ? 'eye-slash' : 'bar-chart'}"></i>
|
|
868
|
+
${isVisible ? 'Hide' : 'Stats'}
|
|
869
|
+
`;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
resetFilters() {
|
|
873
|
+
this.searchInput.value = '';
|
|
874
|
+
this.collectionFilter.value = '';
|
|
875
|
+
this.dateFilter.value = '';
|
|
876
|
+
|
|
877
|
+
// Reset table rows
|
|
878
|
+
this.rows.forEach(row => row.style.display = '');
|
|
879
|
+
|
|
880
|
+
// Reset cards
|
|
881
|
+
const cards = this.cardsContainer.querySelectorAll('.sitemap-card');
|
|
882
|
+
cards.forEach(card => card.style.display = '');
|
|
883
|
+
|
|
884
|
+
this.updateCounts();
|
|
885
|
+
|
|
886
|
+
// Clear URL parameters
|
|
887
|
+
const url = new URL(window.location);
|
|
888
|
+
url.searchParams.delete('q');
|
|
889
|
+
window.history.replaceState({}, document.title, url);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
copyUrl(event) {
|
|
893
|
+
const url = event.currentTarget.dataset.url;
|
|
894
|
+
navigator.clipboard.writeText(url).then(() => {
|
|
895
|
+
const btn = event.currentTarget;
|
|
896
|
+
const originalIcon = btn.innerHTML;
|
|
897
|
+
btn.innerHTML = '<i class="bi bi-check"></i>';
|
|
898
|
+
btn.classList.add('btn-success');
|
|
899
|
+
btn.classList.remove('btn-outline-secondary');
|
|
900
|
+
|
|
901
|
+
setTimeout(() => {
|
|
902
|
+
btn.innerHTML = originalIcon;
|
|
903
|
+
btn.classList.remove('btn-success');
|
|
904
|
+
btn.classList.add('btn-outline-secondary');
|
|
905
|
+
}, 1500);
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
handleUrlParams() {
|
|
910
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
911
|
+
const searchQuery = urlParams.get('q');
|
|
912
|
+
|
|
913
|
+
if (searchQuery) {
|
|
914
|
+
this.searchInput.value = searchQuery;
|
|
915
|
+
this.performSearch();
|
|
107
916
|
}
|
|
108
917
|
}
|
|
918
|
+
|
|
919
|
+
handleKeyboardShortcuts(event) {
|
|
920
|
+
// Ctrl/Cmd + K to focus search
|
|
921
|
+
if ((event.ctrlKey || event.metaKey) && event.key === 'k') {
|
|
922
|
+
event.preventDefault();
|
|
923
|
+
this.searchInput.focus();
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// Escape to clear search
|
|
927
|
+
if (event.key === 'Escape' && document.activeElement === this.searchInput) {
|
|
928
|
+
this.searchInput.value = '';
|
|
929
|
+
this.performSearch();
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// Utility functions
|
|
934
|
+
isSameDay(date1, date2) {
|
|
935
|
+
return date1.toDateString() === date2.toDateString();
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
isWithinDays(date, days) {
|
|
939
|
+
const now = new Date();
|
|
940
|
+
const diffTime = Math.abs(now - date);
|
|
941
|
+
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
942
|
+
return diffDays <= days;
|
|
943
|
+
}
|
|
109
944
|
}
|
|
110
|
-
</script>
|
|
111
945
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const searchQuery = urlParams.get('q');
|
|
117
|
-
// If a search query was provided, set it as the value of the search bar
|
|
118
|
-
if (searchQuery) {
|
|
119
|
-
document.getElementById('searchBar').value = searchQuery;
|
|
120
|
-
// Call the search function
|
|
121
|
-
searchFunction();
|
|
122
|
-
}
|
|
946
|
+
// Initialize sitemap when DOM is ready
|
|
947
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
948
|
+
new SitemapManager();
|
|
949
|
+
});
|
|
123
950
|
</script>
|