Skip to main content

max / makenotwork

11.5 KB · 219 lines History Blame Raw
1 {# The sidebar rides along on every results swap, out-of-band, so its counts
2 and drill-down always describe the results below. Without this the sidebar
3 is stale from the first filter click until a full page load, which is fatal
4 for faceted browsing: the numbers are what the user steers by.
5
6 htmx restores focus after a swap to an element with the same id, so every
7 control in the sidebar carries a stable one and keyboard position survives.
8 The typeahead input additionally uses hx-preserve, since its value is user
9 state the server does not render.
10
11 Guarded on `oob_sidebar`: the full page includes this same partial inside
12 #results-container, and rendering the sidebar there too would emit it twice
13 with duplicate ids. Only the standalone /discover/results response sets it. #}
14 {# #total-count sits in the page form, outside #results-container, so a plain
15 swap leaves it reading the previous filter's total. It said "13 items" beside
16 a list showing 3. #}
17 {# Search results say how well they matched, in two places.
18
19 .results-tier-break is the one heading, and it sits exactly where the claim
20 it makes becomes true: above it, every row holds at least one word that was
21 typed; below it, every row holds none and is here on character overlap alone.
22 The query orders the tiers and flags the first row of the lower one. A page
23 made entirely of near matches carries the heading at the top, which is the
24 honest rendering under pagination. No heading when not searching, and none
25 for 1-2 character searches, which are substring-only and have no boundary.
26
27 .row-match-note is the per-row half: "Matched 3 of 5 words" on a row holding
28 some but not all of them. It cannot be a heading, because coverage varies row
29 to row and one page can hold a 4-of-5 beside a 1-of-5. Rows with every word
30 say nothing, since that is the unremarkable case; rows with none say nothing
31 either, because the heading above them already did.
32
33 Grid view needs `grid-column: 1 / -1` on .results-tier-break to span the row.
34 Discover has no CSS yet, so these are class hooks like the rest of the sidebar. #}
35 {% if oob_sidebar %}
36 <span class="table-meta" id="total-count" hx-swap-oob="true">{{ count_label }}</span>
37 {% endif %}
38 {% if oob_sidebar && mode == "items" %}
39 <div hx-swap-oob="outerHTML:#discover-sidebar">
40 {% include "partials/discover_sidebar.html" %}
41 </div>
42 {% endif %}
43
44 <div class="results-container results-list" id="results-container-inner">
45 <!-- List View -->
46 <div class="results-table" id="results-table">
47 {% if mode == "projects" %}
48 {% for project in projects %}
49 {% if project.starts_fuzzy_block %}
50 <div class="results-tier-break">
51 <span class="results-tier-label">Near matches</span>
52 <span class="results-tier-hint">None of your words are in these. The spelling is close.</span>
53 </div>
54 {% endif %}
55 <a href="/p/{{ project.slug }}" class="table-row project-row">
56 <span class="row-info"><span class="row-name">{{ project.title }}</span><span class="row-creator">{{ project.creator }}</span>{% if project.match_label.is_some() %}<span class="row-match-note">{{ project.match_label.as_deref().unwrap_or("") }}</span>{% endif %}</span>
57 <span class="row-category">{{ project.category_name.as_deref().unwrap_or(&project.project_type) }}</span>
58 <span class="row-items">{{ project.item_count }}</span>
59 <span class="row-date">{{ project.date }}</span>
60 </a>
61 {% endfor %}
62 {% if projects.is_empty() %}
63 <div class="empty-state">
64 {% if is_search %}
65 <p>Nothing matched your search.</p>
66 <p class="empty-state-hint">Try fewer words, or check the spelling. You can also <a href="/discover">clear all filters</a>.</p>
67 {% else %}
68 <p>No projects found matching your filters.</p>
69 <p class="empty-state-hint">Try <a href="/discover">clearing all filters</a>.</p>
70 {% endif %}
71 </div>
72 {% endif %}
73 {% else %}
74 {% for item in items %}
75 {% if item.starts_fuzzy_block %}
76 <div class="results-tier-break">
77 <span class="results-tier-label">Near matches</span>
78 <span class="results-tier-hint">None of your words are in these. The spelling is close.</span>
79 </div>
80 {% endif %}
81 <div class="table-row table-row-item">
82 <a href="{% if item.is_free %}/i/{{ item.id }}{% else %}/purchase/{{ item.id }}{% endif %}" class="table-row-link">
83 <span class="row-type">{{ item.item_type }}</span>
84 <span class="row-info"><span class="row-name">{{ item.name }}</span><span class="row-creator">{{ item.project }} &middot; {{ item.creator }}</span>{% if item.match_label.is_some() %}<span class="row-match-note">{{ item.match_label.as_deref().unwrap_or("") }}</span>{% endif %}</span>
85 <span class="row-category">{{ item.primary_tag }}</span>
86 <span class="row-price">{{ item.price }}</span>
87 <span class="row-date">{{ item.date }}</span>
88 <span class="discover-row-ai-tier">{{ item.ai_tier }}</span>
89 </a>
90 {% if is_authenticated %}
91 <button class="row-save collection-picker-anchor" data-collection-trigger data-item-id="{{ item.id }}"
92 data-action="onOpenCollectionPicker" data-stop title="Save to collection" aria-label="Save to collection">+</button>
93 {% endif %}
94 </div>
95 {% endfor %}
96 {% if items.is_empty() %}
97 <div class="empty-state">
98 {% if is_search %}
99 <p>Nothing matched your search.</p>
100 <p class="empty-state-hint">Try fewer words, or check the spelling. You can also <a href="/discover">clear all filters</a>.</p>
101 {% else %}
102 <p>No items found matching your filters.</p>
103 <p class="empty-state-hint">Try <a href="/discover">clearing all filters</a>.</p>
104 {% endif %}
105 </div>
106 {% endif %}
107 {% endif %}
108 </div>
109
110 <!-- Grid View -->
111 <div class="results-grid" id="results-grid">
112 {% if mode == "projects" %}
113 {% for project in projects %}
114 {% if project.starts_fuzzy_block %}
115 <div class="results-tier-break">
116 <span class="results-tier-label">Near matches</span>
117 <span class="results-tier-hint">None of your words are in these. The spelling is close.</span>
118 </div>
119 {% endif %}
120 <a href="/p/{{ project.slug }}" class="grid-card">
121 <div class="grid-card-content">
122 <div class="grid-card-title">{{ project.title }}</div>
123 <div class="grid-card-meta">{{ project.creator }}</div>
124 {% if project.match_label.is_some() %}<span class="row-match-note">{{ project.match_label.as_deref().unwrap_or("") }}</span>{% endif %}
125 <div class="grid-card-footer">
126 <span class="grid-card-category">{{ project.category_name.as_deref().unwrap_or(&project.project_type) }}</span>
127 <span class="grid-card-stat">{{ project.item_count }} items</span>
128 </div>
129 </div>
130 </a>
131 {% endfor %}
132 {% if projects.is_empty() %}
133 <div class="empty-state">
134 {% if is_search %}
135 <p>Nothing matched your search.</p>
136 <p class="empty-state-hint">Try fewer words, or check the spelling. You can also <a href="/discover">clear all filters</a>.</p>
137 {% else %}
138 <p>No projects found matching your filters.</p>
139 <p class="empty-state-hint">Try <a href="/discover">clearing all filters</a>.</p>
140 {% endif %}
141 </div>
142 {% endif %}
143 {% else %}
144 {% for item in items %}
145 {% if item.starts_fuzzy_block %}
146 <div class="results-tier-break">
147 <span class="results-tier-label">Near matches</span>
148 <span class="results-tier-hint">None of your words are in these. The spelling is close.</span>
149 </div>
150 {% endif %}
151 <div class="grid-card discover-grid-card">
152 {% if is_authenticated %}
153 <button class="grid-card-save collection-picker-anchor" data-collection-trigger data-item-id="{{ item.id }}"
154 data-action="onOpenCollectionPicker" data-stop title="Save to collection" aria-label="Save to collection">+</button>
155 {% endif %}
156 <a href="{% if item.is_free %}/i/{{ item.id }}{% else %}/purchase/{{ item.id }}{% endif %}" class="discover-card-link">
157 <div class="grid-card-thumbnail">{{ item.item_type }}</div>
158 <div class="grid-card-content">
159 <div class="grid-card-title">{{ item.name }}</div>
160 <div class="grid-card-meta">{{ item.project }} &middot; {{ item.creator }}</div>
161 {% if item.match_label.is_some() %}<span class="row-match-note">{{ item.match_label.as_deref().unwrap_or("") }}</span>{% endif %}
162 <div class="grid-card-footer">
163 <span class="grid-card-category">{{ item.primary_tag }}</span>
164 <span class="grid-card-price">{{ item.price }}</span>
165 <span class="discover-row-ai-tier">{{ item.ai_tier }}</span>
166 </div>
167 </div>
168 </a>
169 </div>
170 {% endfor %}
171 {% if items.is_empty() %}
172 <div class="empty-state">
173 {% if is_search %}
174 <p>Nothing matched your search.</p>
175 <p class="empty-state-hint">Try fewer words, or check the spelling. You can also <a href="/discover">clear all filters</a>.</p>
176 {% else %}
177 <p>No items found matching your filters.</p>
178 <p class="empty-state-hint">Try <a href="/discover">clearing all filters</a>.</p>
179 {% endif %}
180 </div>
181 {% endif %}
182 {% endif %}
183 </div>
184 </div>
185
186 <div class="table-footer">
187 <span class="page-info">Showing {{ showing_start }}-{{ showing_end }} of {{ total_items }}</span>
188 <div class="pagination">
189 {% if total_pages > 0 %}
190 <button {% if current_page <= 1 %}disabled{% endif %}
191 hx-get="/discover/results"
192 hx-target="#results-container"
193 hx-include=".discover-filter"
194 hx-vals='{"page": {{ current_page - 1 }}}'
195 {% if current_page <= 1 %}hx-disabled-elt="this"{% endif %}>Prev</button>
196 {% for p in pagination_range %}
197 <button {% if *p == current_page %}class="is-selected"{% endif %}
198 hx-get="/discover/results"
199 hx-target="#results-container"
200 hx-include=".discover-filter"
201 hx-vals='{"page": {{ p }}}'>{{ p }}</button>
202 {% endfor %}
203 {% if total_pages > 5 %}
204 <button disabled>...</button>
205 <button hx-get="/discover/results"
206 hx-target="#results-container"
207 hx-include=".discover-filter"
208 hx-vals='{"page": {{ total_pages }}}'>{{ total_pages }}</button>
209 {% endif %}
210 <button {% if current_page >= total_pages || total_pages == 0 %}disabled{% endif %}
211 hx-get="/discover/results"
212 hx-target="#results-container"
213 hx-include=".discover-filter"
214 hx-vals='{"page": {{ current_page + 1 }}}'
215 {% if current_page >= total_pages || total_pages == 0 %}hx-disabled-elt="this"{% endif %}>Next</button>
216 {% endif %}
217 </div>
218 </div>
219