Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 169 additions & 10 deletions docs/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ let allUsers = [];
let filteredUsers = [];
let isDataLoaded = false;

// ============================================================================
// CONSTANTS
// ============================================================================
const MAX_FOLLOWERS = 999999999;
const MAX_REPOS = 999999;
const MAX_FORKS = 999999;

// ============================================================================
// INITIALIZATION
// ============================================================================
Expand Down Expand Up @@ -379,8 +386,13 @@ function setupEventListeners() {
randomBtn.type = 'button';
randomBtn.addEventListener('click', pickRandomUser);
}
}

const clearFiltersBtn = document.querySelector('.clear-filters-btn');
if (clearFiltersBtn) {
clearFiltersBtn.addEventListener('click', resetFilters);
}
}

/**
* Get currently visible sorted users matching the displayed order
* Uses the same filtering and sorting logic as the display
Expand Down Expand Up @@ -525,16 +537,16 @@ function getActiveFilters() {
*/
function validateRangeFilters(filters) {
if (filters.minFollowers > filters.maxFollowers) {
document.getElementById('maxFollowersFilter').value = '999999999';
filters.maxFollowers = 999999999;
document.getElementById('maxFollowersFilter').value = String(MAX_FOLLOWERS);
filters.maxFollowers = MAX_FOLLOWERS;
}
if (filters.minRepos > filters.maxRepos) {
document.getElementById('maxReposFilter').value = '999999';
filters.maxRepos = 999999;
document.getElementById('maxReposFilter').value = String(MAX_REPOS);
filters.maxRepos = MAX_REPOS;
}
if (filters.minForks > filters.maxForks) {
document.getElementById('maxForksFilter').value = '999999';
filters.maxForks = 999999;
document.getElementById('maxForksFilter').value = String(MAX_FORKS);
filters.maxForks = MAX_FORKS;
}
}

Expand Down Expand Up @@ -743,6 +755,7 @@ function updateVisibilityAndSort() {
renderCards(sortedUsers);
updateCounts(sortedUsers);
updateResultsMessage(sortedUsers);
updateActiveFiltersIndicator();
}

/**
Expand Down Expand Up @@ -1030,6 +1043,152 @@ function updateResultsMessage(sortedUsers) {
}
}

// ============================================================================
// ACTIVE FILTERS INDICATOR
// ============================================================================
/**
* Update the active filters indicator display
*/
function updateActiveFiltersIndicator() {
const indicator = document.getElementById('activeFiltersIndicator');
const tagsContainer = document.getElementById('activeFiltersTags');
if (!indicator || !tagsContainer) return;

const filters = getActiveFilters();
const sortBy = document.getElementById('sortBy').value;
const activeTags = [];

const sortOption = document.querySelector(`#sortBy option[value="${sortBy}"]`);
if (sortOption && sortBy !== 'followers-desc') {
activeTags.push({
label: `Sort: ${sortOption.textContent.trim()}`,
type: 'sort',
value: sortBy,
});
}

if (filters.searchTerm) {
activeTags.push({
label: `Search: "${filters.searchTerm}"`,
type: 'search',
value: filters.searchTerm,
});
}

if (filters.minFollowers > 0 || filters.maxFollowers < MAX_FOLLOWERS) {
let followersLabel = 'Followers: ';
if (filters.minFollowers > 0 && filters.maxFollowers < MAX_FOLLOWERS) {
followersLabel += `${formatNumber(filters.minFollowers)} - ${formatNumber(filters.maxFollowers)}`;
} else if (filters.minFollowers > 0) {
followersLabel += `${formatNumber(filters.minFollowers)}+`;
} else {
followersLabel += `≤ ${formatNumber(filters.maxFollowers)}`;
}
activeTags.push({
label: followersLabel,
type: 'followers',
});
}
if (filters.minRepos > 0 || filters.maxRepos < MAX_REPOS) {
let reposLabel = 'Repos: ';
if (filters.minRepos > 0 && filters.maxRepos < MAX_REPOS) {
reposLabel += `${filters.minRepos} - ${filters.maxRepos}`;
} else if (filters.minRepos > 0) {
reposLabel += `${filters.minRepos}+`;
} else {
reposLabel += `≤ ${filters.maxRepos}`;
}
activeTags.push({
label: reposLabel,
type: 'repos',
});
}

if (filters.minForks > 0 || filters.maxForks < MAX_FORKS) {
let forksLabel = 'Forks: ';
if (filters.minForks > 0 && filters.maxForks < MAX_FORKS) {
forksLabel += `${filters.minForks} - ${filters.maxForks}`;
} else if (filters.minForks > 0) {
forksLabel += `${filters.minForks}+`;
} else {
forksLabel += `≤ ${filters.maxForks}`;
}
activeTags.push({
label: forksLabel,
type: 'forks',
});
}
if (filters.minStars > 0) {
activeTags.push({
label: `Stars: ${formatNumber(filters.minStars)}+`,
type: 'stars',
});
}
addSelectFilterTag(activeTags, filters, 'sponsorsFilter', 'sponsorsFilter', 'Sponsors', 'sponsors');
addSelectFilterTag(activeTags, filters, 'sponsoringFilter', 'sponsoringFilter', 'Sponsoring', 'sponsoring');
addSelectFilterTag(activeTags, filters, 'avatarAgeFilter', 'avatarAgeFilter', 'Avatar', 'avatar');
addSelectFilterTag(activeTags, filters, 'lastRepoActivityFilter', 'lastRepoActivityFilter', 'Repo Activity', 'repo-activity');
addSelectFilterTag(activeTags, filters, 'lastCommitFilter', 'lastCommitFilter', 'Last Commit', 'commit');

if (filters.languageFilter) {
activeTags.push({
label: `Language: ${filters.languageFilter}`,
type: 'language',
});
}
// Clear existing tags
tagsContainer.innerHTML = '';
// Show/hide indicator based on active filters
if (activeTags.length > 0) {
indicator.style.display = 'block';
activeTags.forEach((tag) => {
const tagElement = document.createElement('span');
tagElement.className = 'filter-tag';
tagElement.setAttribute('data-filter-type', tag.type);
tagElement.textContent = tag.label;
tagsContainer.appendChild(tagElement);
});
} else {
indicator.style.display = 'none';
}
}
/**
* Format number with K, M suffixes for display
* @param {number} num - Number to format
* @returns {string} Formatted number string
*/
function formatNumber(num) {
if (num >= 1000000) {
return `${(num / 1000000).toFixed(1)}M`;
}
if (num >= 1000) {
return `${(num / 1000).toFixed(1)}K`;
}
return num.toString();
}

/**
* Add a select-based filter tag to activeTags if the filter is active
* @param {Array} activeTags - Array to add the tag to
* @param {Object} filters - Filters object
* @param {string} filterKey - Key in filters object
* @param {string} elementId - ID of the select element
* @param {string} labelPrefix - Prefix for the tag label (e.g., "Sponsors")
* @param {string} tagType - Type identifier for the tag
*/
function addSelectFilterTag(activeTags, filters, filterKey, elementId, labelPrefix, tagType) {
if (filters[filterKey] !== 'any') {
const select = document.getElementById(elementId);
const option = select?.options[select.selectedIndex];
if (option) {
activeTags.push({
label: `${labelPrefix}: ${option.textContent.trim()}`,
type: tagType,
});
}
}
}

// ============================================================================
// RESET FILTERS
// ============================================================================
Expand All @@ -1041,11 +1200,11 @@ function resetFilters() {
searchInput: '',
sortBy: 'followers-desc',
followersFilter: '0',
maxFollowersFilter: '999999999',
maxFollowersFilter: String(MAX_FOLLOWERS),
minReposFilter: '0',
maxReposFilter: '999999',
maxReposFilter: String(MAX_REPOS),
minForksFilter: '0',
maxForksFilter: '999999',
maxForksFilter: String(MAX_FORKS),
sponsorsFilter: 'any',
sponsoringFilter: 'any',
avatarAgeFilter: 'any',
Expand Down
119 changes: 119 additions & 0 deletions docs/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,125 @@ h1 {
padding-right: 20px;
}

/* ============================================================================ */
/* ACTIVE FILTERS INDICATOR */
/* ============================================================================ */
.active-filters-indicator {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border: 2px solid #667eea;
border-radius: 12px;
padding: 16px 20px;
margin: 20px auto;
max-width: 1400px;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.15);
}

.active-filters-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
flex-wrap: wrap;
gap: 10px;
}

.active-filters-title {
font-weight: 600;
font-size: 14px;
color: #333;
display: flex;
align-items: center;
gap: 6px;
}

.active-filters-title::before {
content: '';
display: inline-block;
width: 16px;
height: 16px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23333' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cpath d='m21 21-4.35-4.35'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-size: contain;
background-position: center;
vertical-align: middle;
}

.clear-filters-btn {
padding: 6px 14px;
background: rgba(255, 100, 100, 0.1);
border: 1px solid rgba(255, 100, 100, 0.3);
color: #d32f2f;
border-radius: 6px;
cursor: pointer;
font-size: 12px;
font-weight: 500;
transition: all 0.2s;
}

.clear-filters-btn:hover {
background: rgba(255, 100, 100, 0.2);
border-color: rgba(255, 100, 100, 0.5);
transform: translateY(-1px);
}

.active-filters-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
}

.filter-tag {
display: inline-flex;
align-items: center;
padding: 6px 12px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 20px;
font-size: 12px;
font-weight: 500;
box-shadow: 0 2px 6px rgba(102, 126, 234, 0.3);
transition: all 0.2s;
}

.filter-tag:hover {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(102, 126, 234, 0.4);
}

.filter-tag[data-filter-type='sort'] {
background: linear-gradient(135deg, #ff9f43 0%, #ff6b6b 100%);
}

.filter-tag[data-filter-type='search'] {
background: linear-gradient(135deg, #4ecdc4 0%, #44a08d 100%);
}

@media (max-width: 768px) {
.active-filters-indicator {
margin: 15px 10px;
padding: 12px 16px;
}

.active-filters-header {
flex-direction: column;
align-items: flex-start;
}

.clear-filters-btn {
width: 100%;
padding: 8px;
}

.active-filters-tags {
gap: 6px;
}

.filter-tag {
font-size: 11px;
padding: 5px 10px;
}
}

/* ============================================================================ */
/* FEATURED USER SECTION */
/* ============================================================================ */
Expand Down
8 changes: 8 additions & 0 deletions layouts/layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,14 @@ <h2 id="featuredUserName" class="featured-name"></h2>
</div>
</div>

<div class="active-filters-indicator" id="activeFiltersIndicator" style="display: none">
<div class="active-filters-header">
<span class="active-filters-title">Active Filters & Sort:</span>
<button class="clear-filters-btn" aria-label="Clear all filters">Clear All</button>
</div>
<div class="active-filters-tags" id="activeFiltersTags"></div>
</div>

<div class="grid" id="grid"></div>
</main>
</div>
Expand Down