Skip to content
191 changes: 185 additions & 6 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1462,7 +1462,7 @@
<span id="languageLogicHint" class="text-xs text-primary font-medium italic"></span>
</div>
</div>

<!-- ═══════════════════ MOBILE FILTER DROPDOWNS ═══════════════════ -->
<div class="mb-4 md:hidden fade-up" style="animation-delay:.3s">
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
<!-- Row 1: Filters + Languages side by side -->
Expand Down Expand Up @@ -1543,7 +1543,31 @@
<div class="flex flex-wrap items-center justify-between gap-3 mb-8">
<p class="font-label text-xs uppercase tracking-widest text-zinc-500">Showing <strong id="orgCount" data-org-count>184</strong> of <span data-org-total>184</span> organizations</p>
<div class="flex flex-wrap items-center gap-2 w-full md:w-auto">
<!-- Export Dropdown -->
<div class="relative" id="exportDropdownWrapper">
<button id="exportBtn" onclick="toggleExportDropdown()" aria-haspopup="true" aria-expanded="false"
aria-controls="exportDropdownMenu" title="Export filtered results"
class="flex items-center gap-1.5 px-4 py-2 rounded-full bg-surface-container-highest text-xs font-bold hover:bg-primary hover:text-white transition-colors disabled:opacity-40 disabled:cursor-not-allowed disabled:pointer-events-none">
<span class="material-symbols-outlined text-sm">download</span>
Export
<span class="material-symbols-outlined text-sm" id="exportChevron">expand_more</span>
</button>
<div id="exportDropdownMenu" role="group" aria-label="Export format"
class="hidden absolute right-0 mt-2 w-44 bg-white dark:bg-[#18181b] border border-zinc-200 dark:border-zinc-700 rounded-xl shadow-xl z-20 overflow-hidden">

Check warning on line 1556 in index.html

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use <address> or <details> or <fieldset> or <optgroup> instead of the group role to ensure accessibility across all devices.

See more on https://sonarcloud.io/project/issues?id=S3DFX-CYBER_GSoC-Org-Finder-&issues=AZ_iUn0tdXJzFvaF79Dh&open=AZ_iUn0tdXJzFvaF79Dh&pullRequest=1883
<button onclick="exportResults('csv')"
class="w-full flex items-center gap-2 px-4 py-3 text-xs font-semibold text-zinc-700 dark:text-zinc-200 hover:bg-orange-50 dark:hover:bg-zinc-800 hover:text-primary transition-colors">
<span class="material-symbols-outlined text-sm">description</span>
Download CSV
</button>
<button onclick="exportResults('json')"
class="w-full flex items-center gap-2 px-4 py-3 text-xs font-semibold text-zinc-700 dark:text-zinc-200 hover:bg-orange-50 dark:hover:bg-zinc-800 hover:text-primary transition-colors">
<span class="material-symbols-outlined text-sm">data_object</span>
Download JSON
</button>
</div>
</div>
<select id="categoryFilter" aria-label="Filter by category" class="flex-1 md:flex-none bg-surface-container-low border-none rounded-xl text-xs font-bold focus:ring-2 focus:ring-zinc-500 focus:outline-none cursor-pointer text-zinc-600 px-2 py-2">

<option value="all">Categories: All</option>
<option value="web">Web</option>
<option value="programming">Programming</option>
Expand Down Expand Up @@ -1577,7 +1601,11 @@
</div>

<!-- Hidden search input for JavaScript compatibility -->
<input type="hidden" id="searchInput" />
<input
type="hidden"
id="searchInput"
aria-label="Organization search query"
/>

<div class="org-grid grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6 scrollable-directory org-directory" id="orgGrid">
<!-- Organizations will be rendered here dynamically -->
Expand Down Expand Up @@ -1630,7 +1658,13 @@
<div class="mt-2 flex items-center justify-between">
<label class="text-xs text-primary font-bold cursor-pointer hover:underline flex items-center gap-1">
<span class="material-symbols-outlined text-[14px]">upload_file</span> Upload .txt
<input type="file" id="aiResumeFile" accept=".txt" class="hidden">
<input
type="file"
id="aiResumeFile"
accept=".txt"
class="hidden"
aria-label="Upload resume text file"
/>
</label>
</div>
</div>
Expand Down Expand Up @@ -2134,11 +2168,11 @@
<header class="proposal-header">
<div class="proposal-header-title">
<h2 id="pwOrgName">Organization Proposal</h2>
<label class="proposal-autosave">
<label for="pwAutosaveToggle" class="proposal-autosave">
<input type="checkbox" id="pwAutosaveToggle" aria-describedby="pwPrivacyNote" />
Enable autosave
<p id="pwPrivacyNote">Drafts are stored only on this device when autosave is enabled.</p>
</label>
<p id="pwPrivacyNote">Drafts are stored only on this device when autosave is enabled.</p>
<div id="pwProposalStats" class="proposal-stats" aria-live="polite"></div>
</div>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
<div class="proposal-toolbar">
Expand Down Expand Up @@ -3447,6 +3481,7 @@
grid.classList.add('hidden');
if (emptyState) emptyState.classList.remove('hidden');
document.getElementById('orgCount').textContent = '0';
updateExportButtonState(0);
document.getElementById('loadMoreContainer').style.display = 'none';
document.getElementById('orgPagination').innerHTML = '';
document.getElementById('orgPagination').style.display = 'none';
Expand Down Expand Up @@ -3511,6 +3546,7 @@
});

document.getElementById('orgCount').textContent = filteredOrgs.length;
updateExportButtonState(filteredOrgs.length);
document.getElementById('loadMoreContainer').style.display = 'none';

renderPaginationControls('orgPagination', filteredOrgs.length, orgCurrentPage, (page) => {
Expand Down Expand Up @@ -3881,6 +3917,13 @@
closeAn();
} else if (document.getElementById('orgPickerModal')?.style.display === 'flex') {
closeOrgPicker();
} else if (!document.getElementById('exportDropdownMenu')?.classList.contains('hidden')) {
const menu = document.getElementById('exportDropdownMenu');
const btn = document.getElementById('exportBtn');
const chevron = document.getElementById('exportChevron');
menu.classList.add('hidden');
if (btn) btn.setAttribute('aria-expanded', 'false');
if (chevron) chevron.textContent = 'expand_more';
}
});

Expand Down Expand Up @@ -5801,8 +5844,17 @@
<button onclick="closeOrgPicker()" aria-label="Close" style="background:none;border:none;cursor:pointer;font-size:20px;color:#71717a;">βœ•</button>
</div>
<div style="padding:0 24px 12px;">
<input id="orgPickerSearch" type="text" placeholder="Search organizations..."
<label for="orgPickerSearch" class="sr-only">
Search organizations
</label>

<input
id="orgPickerSearch"
type="text"
placeholder="Search organizations."
oninput="filterOrgPicker(this.value)"
aria-label="Search organizations"

style="width:100%;padding:10px 14px;border:1px solid #e4e4e7;border-radius:8px;font-size:14px;outline:none;font-family:'Space Grotesk',sans-serif;box-sizing:border-box;">
</div>
<div id="orgPickerList" style="overflow-y:auto;flex:1;padding:0 24px 24px;display:flex;flex-direction:column;gap:6px;"></div>
Expand All @@ -5812,6 +5864,133 @@
document.getElementById('orgPickerModal').addEventListener('click', function(e) {
if (e.target === this) closeOrgPicker();
});

// ─── EXPORT FILTERED RESULTS ─────────────────────────────────────────────────

function toggleExportDropdown() {
const menu = document.getElementById('exportDropdownMenu');
const btn = document.getElementById('exportBtn');
const chevron = document.getElementById('exportChevron');
const isOpen = !menu.classList.contains('hidden');

if (isOpen) {
menu.classList.add('hidden');
btn.setAttribute('aria-expanded', 'false');
chevron.textContent = 'expand_more';
} else {
menu.classList.remove('hidden');
btn.setAttribute('aria-expanded', 'true');
chevron.textContent = 'expand_less';
}
}

// Close dropdown when clicking outside
document.addEventListener('click', function(e) {
const wrapper = document.getElementById('exportDropdownWrapper');
if (wrapper && !wrapper.contains(e.target)) {
const menu = document.getElementById('exportDropdownMenu');
const btn = document.getElementById('exportBtn');
const chevron = document.getElementById('exportChevron');
if (menu) menu.classList.add('hidden');
if (btn) btn.setAttribute('aria-expanded', 'false');
if (chevron) chevron.textContent = 'expand_more';
}
});

function downloadFile(content, filename, type) {
const blob = new Blob([content], { type });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
}

function showExportToast(msg) {
const toast = document.getElementById('exportToast');
if (!toast) return;
toast.textContent = msg;
toast.classList.remove('hidden');
setTimeout(() => toast.classList.add('hidden'), 3000);
}

function exportResults(format) {
// Close the dropdown
const menu = document.getElementById('exportDropdownMenu');
const btn = document.getElementById('exportBtn');
const chevron = document.getElementById('exportChevron');
if (menu) menu.classList.add('hidden');
if (btn) btn.setAttribute('aria-expanded', 'false');
if (chevron) chevron.textContent = 'expand_more';

Comment thread
coderabbitai[bot] marked this conversation as resolved.
const orgs = (typeof filteredOrgs !== 'undefined' && Array.isArray(filteredOrgs))
? filteredOrgs
: [];

if (!orgs || orgs.length === 0) {
showExportToast('No organizations match the current filters β€” nothing to export.');
return;
}

const timestamp = new Date().toISOString().slice(0, 10); // e.g. 2026-06-15

if (format === 'csv') {
const headers = ['Name', 'Category', 'Tags', 'Competition', 'GSoC Years', 'First Year', 'GitHub URL', 'Ideas Link'];
const escape = v => `"${String(v ?? '').replaceAll('"', '""')}"`;
const rows = orgs.map(org => [
org.name,
org.cat,
(org.tags || []).join('; '),
org.competition,
org.years,
org.firstYear,
githubUrlFromValue(org.github) || '',
org.ideas || ''
].map(escape).join(','));

const csv = [headers.map(escape).join(','), ...rows].join('\n');
downloadFile(csv, `gsoc-orgs-${timestamp}.csv`, 'text/csv;charset=utf-8;');

} else if (format === 'json') {
const exportData = orgs.map(org => ({
name: org.name,
category: org.cat,
languages: org.tags || [],
competition: org.competition,
gsocYears: org.years,
firstYear: org.firstYear,
githubUrl: githubUrlFromValue(org.github) || '',
ideasLink: org.ideas || '',
description: org.desc || ''
}));
downloadFile(JSON.stringify(exportData, null, 2), `gsoc-orgs-${timestamp}.json`, 'application/json');
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}

// Disable export button when 0 results
function updateExportButtonState(count) {
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
const btn = document.getElementById('exportBtn');
if (!btn) return;
if (count === 0) {
btn.setAttribute('disabled', 'true');
btn.title = 'No results to export';
// close dropdown if open
const menu = document.getElementById('exportDropdownMenu');
const chevron = document.getElementById('exportChevron');
if (menu) menu.classList.add('hidden');
btn.setAttribute('aria-expanded', 'false');
if (chevron) chevron.textContent = 'expand_more';
} else {
btn.removeAttribute('disabled');
btn.title = 'Export filtered results';
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</script>
<div id="exportToast" role="alert" aria-live="assertive"
class="hidden fixed bottom-6 left-1/2 -translate-x-1/2 z-50 px-5 py-3 rounded-xl bg-zinc-800 text-white text-sm font-semibold shadow-xl transition-all">
</div>
</body>
</html>
Loading