refactor(js): replace var with let/const and use double quotes

Modernize variable declarations and string literals in main.js for
consistency and to signal immutability intent. Pure style change with
no behavioral differences.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Vinta Chen 2026-03-23 01:34:33 +08:00
parent 31fa9a4c38
commit 028c642a8e
No known key found for this signature in database
GPG Key ID: B93DE4F003C33630

View File

@ -1,40 +1,43 @@
// State
var activeFilter = null; // { type: "cat"|"group", value: "..." }
var activeSort = { col: 'stars', order: 'desc' };
var searchInput = document.querySelector('.search');
var filterBar = document.querySelector('.filter-bar');
var filterValue = document.querySelector('.filter-value');
var filterClear = document.querySelector('.filter-clear');
var noResults = document.querySelector('.no-results');
var rows = document.querySelectorAll('.table tbody tr.row');
var tags = document.querySelectorAll('.tag');
var tbody = document.querySelector('.table tbody');
let activeFilter = null; // { type: "cat"|"group", value: "..." }
let activeSort = { col: "stars", order: "desc" };
const searchInput = document.querySelector(".search");
const filterBar = document.querySelector(".filter-bar");
const filterValue = document.querySelector(".filter-value");
const filterClear = document.querySelector(".filter-clear");
const noResults = document.querySelector(".no-results");
const rows = document.querySelectorAll(".table tbody tr.row");
const tags = document.querySelectorAll(".tag");
const tbody = document.querySelector(".table tbody");
function initRevealSections() {
var sections = document.querySelectorAll('[data-reveal]');
const sections = document.querySelectorAll("[data-reveal]");
if (!sections.length) return;
if (!('IntersectionObserver' in window)) {
if (!("IntersectionObserver" in window)) {
sections.forEach(function (section) {
section.classList.add('is-visible');
section.classList.add("is-visible");
});
return;
}
var observer = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (!entry.isIntersecting) return;
entry.target.classList.add('is-visible');
observer.unobserve(entry.target);
});
}, {
threshold: 0.12,
rootMargin: '0px 0px -8% 0px',
});
const observer = new IntersectionObserver(
function (entries) {
entries.forEach(function (entry) {
if (!entry.isIntersecting) return;
entry.target.classList.add("is-visible");
observer.unobserve(entry.target);
});
},
{
threshold: 0.12,
rootMargin: "0px 0px -8% 0px",
},
);
sections.forEach(function (section, index) {
section.classList.add('will-reveal');
section.style.transitionDelay = Math.min(index * 70, 180) + 'ms';
section.classList.add("will-reveal");
section.style.transitionDelay = Math.min(index * 70, 180) + "ms";
observer.observe(section);
});
}
@ -43,34 +46,36 @@ initRevealSections();
// Pause hero animations when scrolled out of view
(function () {
var hero = document.querySelector('.hero');
if (!hero || !('IntersectionObserver' in window)) return;
var observer = new IntersectionObserver(function (entries) {
hero.classList.toggle('offscreen', !entries[0].isIntersecting);
const hero = document.querySelector(".hero");
if (!hero || !("IntersectionObserver" in window)) return;
const observer = new IntersectionObserver(function (entries) {
hero.classList.toggle("offscreen", !entries[0].isIntersecting);
});
observer.observe(hero);
})();
// Relative time formatting
function relativeTime(isoStr) {
var date = new Date(isoStr);
var now = new Date();
var diffMs = now - date;
var diffHours = Math.floor(diffMs / 3600000);
var diffDays = Math.floor(diffMs / 86400000);
if (diffHours < 1) return 'just now';
if (diffHours < 24) return diffHours === 1 ? '1 hour ago' : diffHours + ' hours ago';
if (diffDays === 1) return 'yesterday';
if (diffDays < 30) return diffDays + ' days ago';
var diffMonths = Math.floor(diffDays / 30);
if (diffMonths < 12) return diffMonths === 1 ? '1 month ago' : diffMonths + ' months ago';
var diffYears = Math.floor(diffDays / 365);
return diffYears === 1 ? '1 year ago' : diffYears + ' years ago';
const date = new Date(isoStr);
const now = new Date();
const diffMs = now - date;
const diffHours = Math.floor(diffMs / 3600000);
const diffDays = Math.floor(diffMs / 86400000);
if (diffHours < 1) return "just now";
if (diffHours < 24)
return diffHours === 1 ? "1 hour ago" : diffHours + " hours ago";
if (diffDays === 1) return "yesterday";
if (diffDays < 30) return diffDays + " days ago";
const diffMonths = Math.floor(diffDays / 30);
if (diffMonths < 12)
return diffMonths === 1 ? "1 month ago" : diffMonths + " months ago";
const diffYears = Math.floor(diffDays / 365);
return diffYears === 1 ? "1 year ago" : diffYears + " years ago";
}
// Format all commit date cells
document.querySelectorAll('.col-commit[data-commit]').forEach(function (td) {
var time = td.querySelector('time');
document.querySelectorAll(".col-commit[data-commit]").forEach(function (td) {
const time = td.querySelector("time");
if (time) time.textContent = relativeTime(td.dataset.commit);
});
@ -81,36 +86,37 @@ rows.forEach(function (row, i) {
});
function collapseAll() {
var openRows = document.querySelectorAll('.table tbody tr.row.open');
const openRows = document.querySelectorAll(".table tbody tr.row.open");
openRows.forEach(function (row) {
row.classList.remove('open');
row.setAttribute('aria-expanded', 'false');
row.classList.remove("open");
row.setAttribute("aria-expanded", "false");
});
}
function applyFilters() {
var query = searchInput ? searchInput.value.toLowerCase().trim() : '';
var visibleCount = 0;
const query = searchInput ? searchInput.value.toLowerCase().trim() : "";
let visibleCount = 0;
// Collapse all expanded rows on filter/search change
collapseAll();
rows.forEach(function (row) {
var show = true;
let show = true;
// Category/group filter
if (activeFilter) {
var attr = activeFilter.type === 'cat' ? row.dataset.cats : row.dataset.groups;
show = attr ? attr.split('||').indexOf(activeFilter.value) !== -1 : false;
const attr =
activeFilter.type === "cat" ? row.dataset.cats : row.dataset.groups;
show = attr ? attr.split("||").indexOf(activeFilter.value) !== -1 : false;
}
// Text search
if (show && query) {
if (!row._searchText) {
var text = row.textContent.toLowerCase();
var next = row.nextElementSibling;
if (next && next.classList.contains('expand-row')) {
text += ' ' + next.textContent.toLowerCase();
let text = row.textContent.toLowerCase();
const next = row.nextElementSibling;
if (next && next.classList.contains("expand-row")) {
text += " " + next.textContent.toLowerCase();
}
row._searchText = text;
}
@ -121,7 +127,7 @@ function applyFilters() {
if (show) {
visibleCount++;
var numCell = row.cells[0];
const numCell = row.cells[0];
if (numCell.textContent !== String(visibleCount)) {
numCell.textContent = String(visibleCount);
}
@ -132,19 +138,20 @@ function applyFilters() {
// Update tag highlights
tags.forEach(function (tag) {
var isActive = activeFilter
&& tag.dataset.type === activeFilter.type
&& tag.dataset.value === activeFilter.value;
tag.classList.toggle('active', isActive);
const isActive =
activeFilter &&
tag.dataset.type === activeFilter.type &&
tag.dataset.value === activeFilter.value;
tag.classList.toggle("active", isActive);
});
// Filter bar
if (filterBar) {
if (activeFilter) {
filterBar.classList.add('visible');
filterBar.classList.add("visible");
if (filterValue) filterValue.textContent = activeFilter.value;
} else {
filterBar.classList.remove('visible');
filterBar.classList.remove("visible");
}
}
@ -152,40 +159,46 @@ function applyFilters() {
}
function updateURL() {
var params = new URLSearchParams();
var query = searchInput ? searchInput.value.trim() : '';
if (query) params.set('q', query);
const params = new URLSearchParams();
const query = searchInput ? searchInput.value.trim() : "";
if (query) params.set("q", query);
if (activeFilter) {
params.set(activeFilter.type === 'cat' ? 'category' : 'group', activeFilter.value);
params.set(
activeFilter.type === "cat" ? "category" : "group",
activeFilter.value,
);
}
if (activeSort.col !== 'stars' || activeSort.order !== 'desc') {
params.set('sort', activeSort.col);
params.set('order', activeSort.order);
if (activeSort.col !== "stars" || activeSort.order !== "desc") {
params.set("sort", activeSort.col);
params.set("order", activeSort.order);
}
var qs = params.toString();
history.replaceState(null, '', qs ? '?' + qs : location.pathname);
const qs = params.toString();
history.replaceState(null, "", qs ? "?" + qs : location.pathname);
}
function getSortValue(row, col) {
if (col === 'name') {
return row.querySelector('.col-name a').textContent.trim().toLowerCase();
if (col === "name") {
return row.querySelector(".col-name a").textContent.trim().toLowerCase();
}
if (col === 'stars') {
var text = row.querySelector('.col-stars').textContent.trim().replace(/,/g, '');
var num = parseInt(text, 10);
if (col === "stars") {
const text = row
.querySelector(".col-stars")
.textContent.trim()
.replace(/,/g, "");
const num = parseInt(text, 10);
return isNaN(num) ? -1 : num;
}
if (col === 'commit-time') {
var attr = row.querySelector('.col-commit').getAttribute('data-commit');
if (col === "commit-time") {
const attr = row.querySelector(".col-commit").getAttribute("data-commit");
return attr ? new Date(attr).getTime() : 0;
}
return 0;
}
function sortRows() {
var arr = Array.prototype.slice.call(rows);
var col = activeSort.col;
var order = activeSort.order;
const arr = Array.prototype.slice.call(rows);
const col = activeSort.col;
const order = activeSort.order;
// Cache sort values once to avoid DOM queries per comparison
arr.forEach(function (row) {
@ -193,22 +206,22 @@ function sortRows() {
});
arr.sort(function (a, b) {
var aVal = a._sortVal;
var bVal = b._sortVal;
if (col === 'name') {
var cmp = aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
const aVal = a._sortVal;
const bVal = b._sortVal;
if (col === "name") {
const cmp = aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
if (cmp === 0) return a._origIndex - b._origIndex;
return order === 'desc' ? -cmp : cmp;
return order === "desc" ? -cmp : cmp;
}
if (aVal <= 0 && bVal <= 0) return a._origIndex - b._origIndex;
if (aVal <= 0) return 1;
if (bVal <= 0) return -1;
var cmp = aVal - bVal;
const cmp = aVal - bVal;
if (cmp === 0) return a._origIndex - b._origIndex;
return order === 'desc' ? -cmp : cmp;
return order === "desc" ? -cmp : cmp;
});
var frag = document.createDocumentFragment();
const frag = document.createDocumentFragment();
arr.forEach(function (row) {
frag.appendChild(row);
frag.appendChild(row._expandRow);
@ -218,40 +231,43 @@ function sortRows() {
}
function updateSortIndicators() {
document.querySelectorAll('th[data-sort]').forEach(function (th) {
th.classList.remove('sort-asc', 'sort-desc');
document.querySelectorAll("th[data-sort]").forEach(function (th) {
th.classList.remove("sort-asc", "sort-desc");
if (activeSort && th.dataset.sort === activeSort.col) {
th.classList.add('sort-' + activeSort.order);
th.setAttribute('aria-sort', activeSort.order === 'asc' ? 'ascending' : 'descending');
th.classList.add("sort-" + activeSort.order);
th.setAttribute(
"aria-sort",
activeSort.order === "asc" ? "ascending" : "descending",
);
} else {
th.removeAttribute('aria-sort');
th.removeAttribute("aria-sort");
}
});
}
// Expand/collapse: event delegation on tbody
if (tbody) {
tbody.addEventListener('click', function (e) {
tbody.addEventListener("click", function (e) {
// Don't toggle if clicking a link or tag button
if (e.target.closest('a') || e.target.closest('.tag')) return;
if (e.target.closest("a") || e.target.closest(".tag")) return;
var row = e.target.closest('tr.row');
const row = e.target.closest("tr.row");
if (!row) return;
var isOpen = row.classList.contains('open');
const isOpen = row.classList.contains("open");
if (isOpen) {
row.classList.remove('open');
row.setAttribute('aria-expanded', 'false');
row.classList.remove("open");
row.setAttribute("aria-expanded", "false");
} else {
row.classList.add('open');
row.setAttribute('aria-expanded', 'true');
row.classList.add("open");
row.setAttribute("aria-expanded", "true");
}
});
// Keyboard: Enter or Space on focused .row toggles expand
tbody.addEventListener('keydown', function (e) {
if (e.key !== 'Enter' && e.key !== ' ') return;
var row = e.target.closest('tr.row');
tbody.addEventListener("keydown", function (e) {
if (e.key !== "Enter" && e.key !== " ") return;
const row = e.target.closest("tr.row");
if (!row) return;
e.preventDefault();
row.click();
@ -260,13 +276,17 @@ if (tbody) {
// Tag click: filter by category or group
tags.forEach(function (tag) {
tag.addEventListener('click', function (e) {
tag.addEventListener("click", function (e) {
e.preventDefault();
var type = tag.dataset.type;
var value = tag.dataset.value;
const type = tag.dataset.type;
const value = tag.dataset.value;
// Toggle: click same filter again to clear
if (activeFilter && activeFilter.type === type && activeFilter.value === value) {
if (
activeFilter &&
activeFilter.type === type &&
activeFilter.value === value
) {
activeFilter = null;
} else {
activeFilter = { type: type, value: value };
@ -277,31 +297,32 @@ tags.forEach(function (tag) {
// Clear filter
if (filterClear) {
filterClear.addEventListener('click', function () {
filterClear.addEventListener("click", function () {
activeFilter = null;
applyFilters();
});
}
// No-results clear
var noResultsClear = document.querySelector('.no-results-clear');
const noResultsClear = document.querySelector(".no-results-clear");
if (noResultsClear) {
noResultsClear.addEventListener('click', function () {
if (searchInput) searchInput.value = '';
noResultsClear.addEventListener("click", function () {
if (searchInput) searchInput.value = "";
activeFilter = null;
applyFilters();
});
}
// Column sorting
document.querySelectorAll('th[data-sort]').forEach(function (th) {
th.addEventListener('click', function () {
var col = th.dataset.sort;
var defaultOrder = col === 'name' ? 'asc' : 'desc';
var altOrder = defaultOrder === 'asc' ? 'desc' : 'asc';
document.querySelectorAll("th[data-sort]").forEach(function (th) {
th.addEventListener("click", function () {
const col = th.dataset.sort;
const defaultOrder = col === "name" ? "asc" : "desc";
const altOrder = defaultOrder === "asc" ? "desc" : "asc";
if (activeSort && activeSort.col === col) {
if (activeSort.order === defaultOrder) activeSort = { col: col, order: altOrder };
else activeSort = { col: 'stars', order: 'desc' };
if (activeSort.order === defaultOrder)
activeSort = { col: col, order: altOrder };
else activeSort = { col: "stars", order: "desc" };
} else {
activeSort = { col: col, order: defaultOrder };
}
@ -312,20 +333,27 @@ document.querySelectorAll('th[data-sort]').forEach(function (th) {
// Search input
if (searchInput) {
var searchTimer;
searchInput.addEventListener('input', function () {
let searchTimer;
searchInput.addEventListener("input", function () {
clearTimeout(searchTimer);
searchTimer = setTimeout(applyFilters, 150);
});
// Keyboard shortcuts
document.addEventListener('keydown', function (e) {
if (e.key === '/' && !['INPUT', 'TEXTAREA', 'SELECT'].includes(document.activeElement.tagName) && !e.ctrlKey && !e.metaKey) {
document.addEventListener("keydown", function (e) {
if (
e.key === "/" &&
!["INPUT", "TEXTAREA", "SELECT"].includes(
document.activeElement.tagName,
) &&
!e.ctrlKey &&
!e.metaKey
) {
e.preventDefault();
searchInput.focus();
}
if (e.key === 'Escape' && document.activeElement === searchInput) {
searchInput.value = '';
if (e.key === "Escape" && document.activeElement === searchInput) {
searchInput.value = "";
activeFilter = null;
applyFilters();
searchInput.blur();
@ -334,24 +362,24 @@ if (searchInput) {
}
// Back to top
var backToTop = document.querySelector('.back-to-top');
var resultsSection = document.querySelector('#library-index');
var tableWrap = document.querySelector('.table-wrap');
var stickyHeaderCell = backToTop ? backToTop.closest('th') : null;
const backToTop = document.querySelector(".back-to-top");
const resultsSection = document.querySelector("#library-index");
const tableWrap = document.querySelector(".table-wrap");
const stickyHeaderCell = backToTop ? backToTop.closest("th") : null;
function updateBackToTopVisibility() {
if (!backToTop || !tableWrap || !stickyHeaderCell) return;
var tableRect = tableWrap.getBoundingClientRect();
var headRect = stickyHeaderCell.getBoundingClientRect();
var hasPassedHeader = tableRect.top <= 0 && headRect.bottom > 0;
const tableRect = tableWrap.getBoundingClientRect();
const headRect = stickyHeaderCell.getBoundingClientRect();
const hasPassedHeader = tableRect.top <= 0 && headRect.bottom > 0;
backToTop.classList.toggle('visible', hasPassedHeader);
backToTop.classList.toggle("visible", hasPassedHeader);
}
if (backToTop) {
var scrollTicking = false;
window.addEventListener('scroll', function () {
let scrollTicking = false;
window.addEventListener("scroll", function () {
if (!scrollTicking) {
requestAnimationFrame(function () {
updateBackToTopVisibility();
@ -361,12 +389,12 @@ if (backToTop) {
}
});
window.addEventListener('resize', updateBackToTopVisibility);
window.addEventListener("resize", updateBackToTopVisibility);
backToTop.addEventListener('click', function () {
var target = searchInput || resultsSection;
backToTop.addEventListener("click", function () {
const target = searchInput || resultsSection;
if (!target) return;
target.scrollIntoView({ behavior: 'smooth', block: 'center' });
target.scrollIntoView({ behavior: "smooth", block: "center" });
if (searchInput) searchInput.focus();
});
@ -375,16 +403,19 @@ if (backToTop) {
// Restore state from URL
(function () {
var params = new URLSearchParams(location.search);
var q = params.get('q');
var cat = params.get('category');
var group = params.get('group');
var sort = params.get('sort');
var order = params.get('order');
const params = new URLSearchParams(location.search);
const q = params.get("q");
const cat = params.get("category");
const group = params.get("group");
const sort = params.get("sort");
const order = params.get("order");
if (q && searchInput) searchInput.value = q;
if (cat) activeFilter = { type: 'cat', value: cat };
else if (group) activeFilter = { type: 'group', value: group };
if ((sort === 'name' || sort === 'stars' || sort === 'commit-time') && (order === 'desc' || order === 'asc')) {
if (cat) activeFilter = { type: "cat", value: cat };
else if (group) activeFilter = { type: "group", value: group };
if (
(sort === "name" || sort === "stars" || sort === "commit-time") &&
(order === "desc" || order === "asc")
) {
activeSort = { col: sort, order: order };
}
if (q || cat || group || sort) {