diff --git a/pages/index.vue b/pages/index.vue index 261fa2f..1bf0bae 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -13,17 +13,37 @@ const languages = ref([]); const topOfTableRef = ref(); const hasScrolled = ref(false); +function calculateMatchScore(repoName: string, searchTerm: string) { + let score = 0; + let index = 0; + for (const char of searchTerm.toLowerCase()) { + index = repoName.toLowerCase().indexOf(char, index); + if (index === -1) break; + score++; + index++; + } + return score; +} + const repositories = computed(() => { if (!settings.search && !settings.languages.length) return data.value; - return data.value?.filter((repo) => { - const isLanguage = settings.languages.length - ? settings.languages.includes(repo.language?.name ?? '') - : true; - const isSearch = settings.search - ? repo.name.toLowerCase().includes(settings.search.toLowerCase()) - : true; - return isLanguage && isSearch; - }); + + const searchTerm = settings.search.toLowerCase(); + + return data.value + ?.filter((repo) => { + const isLanguage = settings.languages.length + ? settings.languages.includes(repo.language?.name ?? '') + : true; + const isSearch = calculateMatchScore(repo.name, searchTerm) >= searchTerm.length; + return isLanguage && isSearch; + }) + .sort((a, b) => { + const scoreA = calculateMatchScore(a.name, searchTerm); + const scoreB = calculateMatchScore(b.name, searchTerm); + + return scoreB - scoreA; + }); }); const hoverEffect = ref({ height: 0, top: 0, opacity: 0 }); diff --git a/server/api/repositories.ts b/server/api/repositories.ts index 4b73af0..be4d69a 100644 --- a/server/api/repositories.ts +++ b/server/api/repositories.ts @@ -1,5 +1,15 @@ import { emojify } from 'node-emoji'; +function getDisplayName(name: string) { + const camelPascalSplit = name.replace(/([a-z])([A-Z])/g, '$1 $2'); + const intermediate = camelPascalSplit.replace(/[_-]/g, ' '); + const words = intermediate.split(' '); + const capitalizedWords = words.map( + (word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(), + ); + return capitalizedWords.join(' '); +} + export type Repository = { rank: number; name: string; @@ -57,7 +67,7 @@ export default defineEventHandler(async (event) => { const result: Repository[] = data.search.edges.map(({ node }: any, index: number) => ({ rank: index + 1, - name: node.name, + name: getDisplayName(node.name), ownerName: node.owner.login, image: node.owner.avatarUrl, description: emojify(node.description),