Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
18 changes: 12 additions & 6 deletions .github/workflows/program-classification-validator.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,24 +81,30 @@ jobs:
// ------------------------------------
// Metadata
// ------------------------------------

const normalizedBody = body.replace(/\r\n/g, '\n');
Comment thread
desireddymohithreddy0925 marked this conversation as resolved.
const has = (re) => re.test(normalizedBody);

if (
body.includes('program: gssoc') ||
body.includes('- [x] gssoc')
has(/\bprogram\s*:\s*gssoc\b/i) ||
has(/^\s*[-*]\s*\[x\]\s*(gssoc|i am contributing under gssoc|i am participating via gssoc)\b/im) ||
has(/\bprogram\n\s*gssoc\b/i)
) {
detected.add('gssoc');
}

if (
body.includes('program: nsoc') ||
body.includes('- [x] nsoc')
has(/\bprogram\s*:\s*nsoc\b/i) ||
has(/^\s*[-*]\s*\[x\]\s*(nsoc|i am contributing under nsoc|i am participating via nsoc)\b/im) ||
has(/\bprogram\n\s*nsoc\b/i)
) {
detected.add('nsoc');
}

if (
body.includes('program: general') ||
body.includes('- [x] general contribution')
has(/\bprogram\s*:\s*general\b/i) ||
has(/^\s*[-*]\s*\[x\]\s*(general contribution|i am a general contributor)\b/im) ||
has(/\bprogram\n\s*general\b/i)
) {
detected.add('general');
}
Expand Down
97 changes: 68 additions & 29 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,35 +38,69 @@
}
</script>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script src="https://cdn.jsdelivr.net/npm/marked@9.1.6/marked.min.js" integrity="sha384-odPBjvtXVM/5hOYIr3A1dB+flh0c3wAT3bSesIOqEGmyUA4JoKf/YTWy0XKOYAY7" crossorigin="anonymous"></script>
<script>
if (typeof marked === 'undefined') {
const s = document.createElement('script');
s.src = "https://unpkg.com/marked@9.1.6/marked.min.js";
s.integrity = "sha384-odPBjvtXVM/5hOYIr3A1dB+flh0c3wAT3bSesIOqEGmyUA4JoKf/YTWy0XKOYAY7";
s.crossOrigin = "anonymous";
document.head.appendChild(s);
}
</script>
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.4.0/dist/purify.min.js" integrity="sha384-lExbHoPFNjOW+xMze1tJWzpODN3bi/yx3zhbwRoZWaIYxjl89HxJcI2BxPTNa9M7" crossorigin="anonymous"></script>
<script>
if (typeof DOMPurify === 'undefined') {
const s = document.createElement('script');
s.src = "https://unpkg.com/dompurify@3.4.0/dist/purify.min.js";
s.integrity = "sha384-lExbHoPFNjOW+xMze1tJWzpODN3bi/yx3zhbwRoZWaIYxjl89HxJcI2BxPTNa9M7";
s.crossOrigin = "anonymous";
document.head.appendChild(s);
}
</script>
<script src="https://cdn.jsdelivr.net/npm/html2pdf.js@0.14.0/dist/html2pdf.bundle.min.js" integrity="sha384-EaWTV/aVUkLz3tfwg3+5ycX7Q/d9ET9ruOKUgUuFIRUCfzHO1eo2J62a844iWPmY" crossorigin="anonymous"></script>
<script>
if (typeof html2pdf === 'undefined') {
const s = document.createElement('script');
s.src = "https://unpkg.com/html2pdf.js@0.14.0/dist/html2pdf.bundle.min.js";
s.integrity = "sha384-EaWTV/aVUkLz3tfwg3+5ycX7Q/d9ET9ruOKUgUuFIRUCfzHO1eo2J62a844iWPmY";
s.crossOrigin = "anonymous";
document.head.appendChild(s);
}
// Utility to load third-party scripts with primary and fallback CDNs safely.
(function loadDependencies() {
const dependencies = [
{
name: 'fuse',
primary: 'https://cdn.jsdelivr.net/npm/fuse.js@7.0.0/dist/fuse.min.js',
fallback: 'https://unpkg.com/fuse.js@7.0.0/dist/fuse.min.js',
integrity: '', // No integrity for ease of fallback unless specified
Comment thread
desireddymohithreddy0925 marked this conversation as resolved.
check: () => typeof Fuse !== 'undefined'
},
{
name: 'marked',
primary: 'https://cdn.jsdelivr.net/npm/marked@9.1.6/marked.min.js',
fallback: 'https://unpkg.com/marked@9.1.6/marked.min.js',
integrity: 'sha384-odPBjvtXVM/5hOYIr3A1dB+flh0c3wAT3bSesIOqEGmyUA4JoKf/YTWy0XKOYAY7',
check: () => typeof marked !== 'undefined'
},
{
name: 'DOMPurify',
primary: 'https://cdn.jsdelivr.net/npm/dompurify@3.4.0/dist/purify.min.js',
fallback: 'https://unpkg.com/dompurify@3.4.0/dist/purify.min.js',
integrity: 'sha384-lExbHoPFNjOW+xMze1tJWzpODN3bi/yx3zhbwRoZWaIYxjl89HxJcI2BxPTNa9M7',
check: () => typeof DOMPurify !== 'undefined'
},
{
name: 'html2pdf',
primary: 'https://cdn.jsdelivr.net/npm/html2pdf.js@0.14.0/dist/html2pdf.bundle.min.js',
fallback: 'https://unpkg.com/html2pdf.js@0.14.0/dist/html2pdf.bundle.min.js',
integrity: 'sha384-EaWTV/aVUkLz3tfwg3+5ycX7Q/d9ET9ruOKUgUuFIRUCfzHO1eo2J62a844iWPmY',
check: () => typeof html2pdf !== 'undefined'
}
];

function injectScript(src, integrity, onload, onerror) {
const s = document.createElement('script');
s.src = src;
if (integrity) {
s.integrity = integrity;
s.crossOrigin = 'anonymous';
}
s.onload = onload;
s.onerror = onerror;
document.head.appendChild(s);
}

dependencies.forEach(dep => {
injectScript(
dep.primary,
dep.integrity,
() => {
if (!dep.check()) {
console.warn(`Primary CDN failed validation for ${dep.name}, trying fallback...`);
injectScript(dep.fallback, dep.integrity, null, () => console.error(`Fallback failed for ${dep.name}`));
}
},
() => {
console.warn(`Primary CDN failed to load for ${dep.name}, trying fallback...`);
injectScript(dep.fallback, dep.integrity, null, () => console.error(`Fallback failed for ${dep.name}`));
}
);
});
})();
</script>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=Space+Grotesk:wght@400;500;600;700&family=Fira+Code:wght@400;500&display=swap" rel="stylesheet" />
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet" />
Expand All @@ -75,7 +109,12 @@
// Theme restoration - runs early to prevent FOUC
(function(){
try {
if (localStorage.getItem('theme') === 'dark') {
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'dark') {
document.documentElement.classList.add('dark');
} else if (savedTheme === 'light') {
document.documentElement.classList.remove('dark');
} else if (globalThis.matchMedia?.('(prefers-color-scheme: dark)').matches) {
document.documentElement.classList.add('dark');
}
} catch(e) {
Expand Down
55 changes: 52 additions & 3 deletions src/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,26 @@
// ══════════════════════════════════════════════
(function initTheme() {
try {
const saved = localStorage.getItem('theme') || 'light';
document.documentElement.classList.toggle('dark', saved === 'dark');
const saved = localStorage.getItem('theme');
Comment thread
desireddymohithreddy0925 marked this conversation as resolved.
if (saved === 'dark') {
document.documentElement.classList.add('dark');
} else if (saved === 'light') {
document.documentElement.classList.remove('dark');
} else if (globalThis.matchMedia?.('(prefers-color-scheme: dark)').matches) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
updateThemeIcon();

if (globalThis.matchMedia) {
globalThis.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
Comment thread
desireddymohithreddy0925 marked this conversation as resolved.
if (!localStorage.getItem('theme')) {
document.documentElement.classList.toggle('dark', e.matches);
updateThemeIcon();
}
});
}
} catch (e) {
console.warn('Theme init failed:', e);
}
Expand Down Expand Up @@ -1071,17 +1088,48 @@
if (nameB === search && nameA !== search) return 1;
if (nameA.startsWith(search) && !nameB.startsWith(search)) return -1;
if (nameB.startsWith(search) && !nameA.startsWith(search)) return 1;

if (globalThis.orgSearchScores && globalThis.orgSearchScores.has(a.name) && globalThis.orgSearchScores.has(b.name)) {

Check warning on line 1092 in src/js/app.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer using an optional chain expression instead, as it's more concise and easier to read.

See more on https://sonarcloud.io/project/issues?id=S3DFX-CYBER_GSoC-Org-Finder-&issues=AZ8AaJLZaD1_UBKZbWbz&open=AZ8AaJLZaD1_UBKZbWbz&pullRequest=1961
const diff = globalThis.orgSearchScores.get(a.name) - globalThis.orgSearchScores.get(b.name);
if (diff !== 0) return diff;
}

return applySecondarySort(a, b, sort);
}

let orgSearchFuse = null;

function applyFilters() {

Check failure on line 1102 in src/js/app.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 22 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=S3DFX-CYBER_GSoC-Org-Finder-&issues=AZ8AaJLZaD1_UBKZbWb0&open=AZ8AaJLZaD1_UBKZbWb0&pullRequest=1961
const search = (document.getElementById('searchInput')?.value || '').trim().toLowerCase();
const categoryValue = document.getElementById('categoryFilter')?.value || 'all';
const cat = categoryValue === 'all' ? '' : categoryValue;
const compF = document.getElementById('complexityFilter')?.value || 'all';
const sort = document.getElementById('sortSelect')?.value || 'alpha';

filteredOrgs = ORGS.filter(o => matchesFilters(o, cat, compF, search));
globalThis.orgSearchScores = new Map();
let baseOrgs = ORGS;
let useFallbackSearch = false;

if (search) {
if (typeof Fuse !== 'undefined') {

Check warning on line 1114 in src/js/app.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unexpected negated condition.

See more on https://sonarcloud.io/project/issues?id=S3DFX-CYBER_GSoC-Org-Finder-&issues=AZ8AaJLZaD1_UBKZbWb1&open=AZ8AaJLZaD1_UBKZbWb1&pullRequest=1961
if (!orgSearchFuse) {
orgSearchFuse = new Fuse(ORGS, {
includeScore: true,
threshold: 0.3,
keys: ['name', 'tags', 'cat']
});
}
const results = orgSearchFuse.search(search);
baseOrgs = results.map(r => {
globalThis.orgSearchScores.set(r.item.name, r.score);
return r.item;
});
} else {
useFallbackSearch = true;
}
}

filteredOrgs = baseOrgs.filter(o => matchesFilters(o, cat, compF, useFallbackSearch ? search : ''));

// Smart sorting: Exact match first, startsWith second, alphabetic/secondary sort third
if (search) {
Expand Down Expand Up @@ -2622,6 +2670,7 @@
githubUrlFromValue,
orgMatchesLanguages,
applySecondarySort,
searchComparator,
openModal,
renderModalHeader,
closeModal,
Expand Down
28 changes: 27 additions & 1 deletion tests/filtering.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ globalThis.document = {
globalThis.ORGS = require('../src/js/org.js');

// Import the modules
const { orgMatchesLanguages, applySecondarySort } = require('../src/js/app.js');
const { orgMatchesLanguages, applySecondarySort, searchComparator } = require('../src/js/app.js');
require('../src/js/skillExtractor.js'); // Populates LANGUAGE_MAP on global

test('orgMatchesLanguages returns true when languages set is empty', () => {
Expand Down Expand Up @@ -76,3 +76,29 @@ test('applySecondarySort sorts correctly', () => {
// alphabetical sort (default)
assert.ok(applySecondarySort(a, b, 'alpha') < 0);
});

test('searchComparator sorts with fuzzy scores', () => {
const a = { name: 'The Python Foundation', years: 10, competition: 'hot' };
const b = { name: 'A Pyhton Group', years: 5, competition: 'chill' };
const c = { name: 'Some Other', years: 5, competition: 'chill' };

globalThis.orgSearchScores = new Map();
globalThis.orgSearchScores.set('The Python Foundation', 0.1);
globalThis.orgSearchScores.set('A Pyhton Group', 0.3);
globalThis.orgSearchScores.set('Some Other', 0.9);

// a has better score than b
assert.ok(searchComparator(a, b, 'pyhton', 'alpha') < 0);
// b has better score than c
assert.ok(searchComparator(b, c, 'pyhton', 'alpha') < 0);

// Exact match takes precedence over score
const exact = { name: 'pyhton', years: 1, competition: 'chill' };
globalThis.orgSearchScores.set('pyhton', 0.5); // Worse score somehow
assert.ok(searchComparator(exact, a, 'pyhton', 'alpha') < 0);

// StartsWith takes precedence over score
const starts = { name: 'pyhton starts', years: 1, competition: 'chill' };
globalThis.orgSearchScores.set('pyhton starts', 0.4);
assert.ok(searchComparator(starts, b, 'pyhton', 'alpha') < 0);
});