Skip to content

Commit d4156ac

Browse files
authored
Merge pull request #76 from arm-university/v3x-patch-push
re-ordering fix
2 parents f7632e4 + 89c0221 commit d4156ac

File tree

1 file changed

+96
-46
lines changed

1 file changed

+96
-46
lines changed

docs/_layouts/article.html

Lines changed: 96 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -178,68 +178,115 @@ <h2 class="project-group-title">{{ category.title }}</h2>
178178
{%- endfor -%}
179179
</div>
180180

181-
<script>
182-
// Put projects with badge key "recently_added" first (per category list)
183-
function hasRecentlyAddedBadge(li) {
184-
let badges = [];
181+
<script>
182+
(function () {
183+
let isSorting = false;
184+
let observer = null;
185+
let debounceTimer = null;
186+
187+
function safeParseBadges(raw) {
188+
raw = raw == null ? '' : String(raw).trim();
189+
if (!raw) return [];
185190
try {
186-
badges = JSON.parse(li.dataset.badges || '[]');
187-
} catch (e) {
188-
badges = [];
189-
}
190-
return Array.isArray(badges) && badges.includes('recently_added');
191+
const j = JSON.parse(raw);
192+
if (Array.isArray(j)) return j.map(x => String(x).trim()).filter(Boolean);
193+
} catch (e) {}
194+
// comma-separated fallback
195+
const cleaned = raw.replace(/^[\[\]'" ]+|[\[\]'" ]+$/g, '');
196+
return cleaned.split(',').map(s => s.trim()).filter(Boolean);
197+
}
198+
199+
function badgesFromDOM(li) {
200+
const badgesEl = li.querySelector('.project-badges');
201+
if (!badgesEl) return [];
202+
// collect visible badge labels
203+
return Array.from(badgesEl.querySelectorAll('*'))
204+
.map(n => (n.textContent || '').trim())
205+
.filter(Boolean);
206+
}
207+
208+
function normalize(arr) {
209+
return (arr || []).map(x => String(x).trim().toLowerCase());
210+
}
211+
212+
function badgeRank(normed) {
213+
const isTrending = normed.includes('trending');
214+
const isRecent =
215+
normed.includes('recently_added') ||
216+
normed.includes('recently-added') ||
217+
normed.includes('recentlyadded');
218+
219+
if (isTrending && isRecent) return 0; // Trending + Recently added
220+
if (isRecent) return 1; // Recently added
221+
if (isTrending) return 2; // Trending
222+
return 3; // Neither
191223
}
192224

193-
function sortProjectsRecentlyAddedFirst() {
225+
function getBadgesForItem(li) {
226+
const raw = li.getAttribute('data-badges') || li.dataset.badges || '';
227+
let parsed = safeParseBadges(raw);
228+
if (parsed.length === 0) parsed = badgesFromDOM(li);
229+
return normalize(parsed);
230+
}
231+
232+
function sortProjectLists() {
233+
if (isSorting) return;
234+
isSorting = true;
235+
236+
// Prevent observer loop while we move nodes
237+
if (observer) observer.disconnect();
238+
194239
document.querySelectorAll('.project-list').forEach(ul => {
195240
const items = Array.from(ul.querySelectorAll(':scope > .project-item'));
196241

197-
// stable sort: decorate with original index
198-
const decorated = items.map((el, idx) => ({
199-
el,
200-
idx,
201-
isRecent: hasRecentlyAddedBadge(el)
202-
}));
203-
204-
decorated.sort((a, b) => {
205-
if (a.isRecent !== b.isRecent) return a.isRecent ? -1 : 1; // recent first
206-
return a.idx - b.idx; // preserve original order otherwise
242+
const decorated = items.map((el, idx) => {
243+
const badges = getBadgesForItem(el);
244+
return { el, idx, rank: badgeRank(badges) };
207245
});
208246

247+
decorated.sort((a, b) => (a.rank - b.rank) || (a.idx - b.idx));
209248
decorated.forEach(d => ul.appendChild(d.el));
210249
});
250+
251+
// Reconnect observer after we're done moving nodes
252+
startObserving();
253+
254+
isSorting = false;
211255
}
212256

213-
document.addEventListener('DOMContentLoaded', () => {
214-
sortProjectsRecentlyAddedFirst();
215-
});
257+
function scheduleSort() {
258+
if (debounceTimer) clearTimeout(debounceTimer);
259+
debounceTimer = setTimeout(sortProjectLists, 100);
260+
}
216261

217-
// --- Existing filtering (unchanged) ---
218-
function filterProjects() {
219-
const checks = Array.from(document.querySelectorAll('.filter-checkbox:checked'));
220-
if (checks.length === 0) {
221-
document.querySelectorAll('.project-item').forEach(el => el.style.display = '');
222-
return;
223-
}
224-
const activeFilters = checks.map(cb => ({ cat: cb.dataset.category, val: cb.value }));
225-
document.querySelectorAll('.project-item').forEach(li => {
226-
const plats = JSON.parse(li.dataset.platforms || '[]');
227-
const subs = JSON.parse(li.dataset.subjects || '[]');
228-
const swhws = JSON.parse(li.dataset.swhw || '[]');
229-
const levels = JSON.parse(li.dataset.supportLevel || '[]');
230-
const ok = activeFilters.every(f => {
231-
switch (f.cat) {
232-
case 'platform': return plats.includes(f.val);
233-
case 'subject': return subs.includes(f.val);
234-
case 'sw-hw': return swhws.includes(f.val);
235-
case 'support-level': return levels.includes(f.val);
236-
default: return true;
237-
}
262+
function startObserving() {
263+
const container = document.querySelector('.project-results');
264+
if (!container) return;
265+
266+
if (!observer) {
267+
observer = new MutationObserver(() => {
268+
// Only schedule if we aren't already sorting
269+
if (!isSorting) scheduleSort();
238270
});
239-
li.style.display = ok ? '' : 'none';
240-
});
271+
}
272+
273+
// IMPORTANT: only observe childList changes, not attributes on the whole subtree.
274+
// Observing attributes widely can cause lots of extra triggers.
275+
observer.observe(container, { childList: true, subtree: true });
241276
}
277+
278+
document.addEventListener('DOMContentLoaded', () => {
279+
sortProjectLists();
280+
// Optional second pass for late-rendered badges
281+
setTimeout(sortProjectLists, 300);
282+
});
283+
284+
// Expose for manual debugging
285+
window.__arm_sortProjectLists = sortProjectLists;
286+
})();
242287
</script>
288+
289+
243290
{%- endif -%}
244291
</div>
245292

@@ -253,3 +300,6 @@ <h2 class="project-group-title">{{ category.title }}</h2>
253300
</div>
254301

255302

303+
304+
305+

0 commit comments

Comments
 (0)