mirror of
https://github.com/maciejpedzich/spotifyplaylistarchive.com.git
synced 2024-09-19 02:06:19 +02:00
Implement fuzzy search for archive entries
This commit is contained in:
parent
b7ae383e65
commit
a30bb573b5
@ -1,4 +1,4 @@
|
||||
export interface SearchResult {
|
||||
id: string;
|
||||
title: string;
|
||||
name: string;
|
||||
}
|
||||
|
83
package-lock.json
generated
83
package-lock.json
generated
@ -11,6 +11,7 @@
|
||||
"@vueuse/nuxt": "^8.7.5",
|
||||
"chart.js": "^3.8.0",
|
||||
"date-fns": "^2.28.0",
|
||||
"fast-fuzzy": "^1.11.2",
|
||||
"format-duration": "^2.0.0",
|
||||
"html-entities": "^2.3.3",
|
||||
"primeflex": "^3.2.1",
|
||||
@ -4884,6 +4885,14 @@
|
||||
"integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fast-fuzzy": {
|
||||
"version": "1.11.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-fuzzy/-/fast-fuzzy-1.11.2.tgz",
|
||||
"integrity": "sha512-H1ct10Pzx+pSO4h7F1uBXET91ay2hy67J1aQZFKL23EXsOoanpwjPNQQoc+NhClKJMmlGGN+0bXhIdFJX70BJw==",
|
||||
"dependencies": {
|
||||
"graphemesplit": "^2.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
"version": "3.2.11",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
|
||||
@ -5405,6 +5414,15 @@
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
|
||||
"integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="
|
||||
},
|
||||
"node_modules/graphemesplit": {
|
||||
"version": "2.4.4",
|
||||
"resolved": "https://registry.npmjs.org/graphemesplit/-/graphemesplit-2.4.4.tgz",
|
||||
"integrity": "sha512-lKrpp1mk1NH26USxC/Asw4OHbhSQf5XfrWZ+CDv/dFVvd1j17kFgMotdJvOesmHkbFX9P9sBfpH8VogxOWLg8w==",
|
||||
"dependencies": {
|
||||
"js-base64": "^3.6.0",
|
||||
"unicode-trie": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/gzip-size": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-7.0.0.tgz",
|
||||
@ -6146,6 +6164,11 @@
|
||||
"jiti": "bin/jiti.js"
|
||||
}
|
||||
},
|
||||
"node_modules/js-base64": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.2.tgz",
|
||||
"integrity": "sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ=="
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@ -7355,6 +7378,11 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/pako": {
|
||||
"version": "0.2.9",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
|
||||
"integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="
|
||||
},
|
||||
"node_modules/parent-module": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||
@ -9369,6 +9397,11 @@
|
||||
"integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/tiny-inflate": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
|
||||
"integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="
|
||||
},
|
||||
"node_modules/tiny-invariant": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz",
|
||||
@ -9596,6 +9629,15 @@
|
||||
"pathe": "^0.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unicode-trie": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz",
|
||||
"integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==",
|
||||
"dependencies": {
|
||||
"pako": "^0.2.5",
|
||||
"tiny-inflate": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unimport": {
|
||||
"version": "0.4.5",
|
||||
"resolved": "https://registry.npmjs.org/unimport/-/unimport-0.4.5.tgz",
|
||||
@ -13824,6 +13866,14 @@
|
||||
"integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
|
||||
"dev": true
|
||||
},
|
||||
"fast-fuzzy": {
|
||||
"version": "1.11.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-fuzzy/-/fast-fuzzy-1.11.2.tgz",
|
||||
"integrity": "sha512-H1ct10Pzx+pSO4h7F1uBXET91ay2hy67J1aQZFKL23EXsOoanpwjPNQQoc+NhClKJMmlGGN+0bXhIdFJX70BJw==",
|
||||
"requires": {
|
||||
"graphemesplit": "^2.4.1"
|
||||
}
|
||||
},
|
||||
"fast-glob": {
|
||||
"version": "3.2.11",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
|
||||
@ -14206,6 +14256,15 @@
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
|
||||
"integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="
|
||||
},
|
||||
"graphemesplit": {
|
||||
"version": "2.4.4",
|
||||
"resolved": "https://registry.npmjs.org/graphemesplit/-/graphemesplit-2.4.4.tgz",
|
||||
"integrity": "sha512-lKrpp1mk1NH26USxC/Asw4OHbhSQf5XfrWZ+CDv/dFVvd1j17kFgMotdJvOesmHkbFX9P9sBfpH8VogxOWLg8w==",
|
||||
"requires": {
|
||||
"js-base64": "^3.6.0",
|
||||
"unicode-trie": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"gzip-size": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-7.0.0.tgz",
|
||||
@ -14726,6 +14785,11 @@
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.14.0.tgz",
|
||||
"integrity": "sha512-4IwstlaKQc9vCTC+qUXLM1hajy2ImiL9KnLvVYiaHOtS/v3wRjhLlGl121AmgDgx/O43uKmxownJghS5XMya2A=="
|
||||
},
|
||||
"js-base64": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.2.tgz",
|
||||
"integrity": "sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ=="
|
||||
},
|
||||
"js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@ -15666,6 +15730,11 @@
|
||||
"integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==",
|
||||
"dev": true
|
||||
},
|
||||
"pako": {
|
||||
"version": "0.2.9",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
|
||||
"integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="
|
||||
},
|
||||
"parent-module": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||
@ -17130,6 +17199,11 @@
|
||||
"integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
|
||||
"dev": true
|
||||
},
|
||||
"tiny-inflate": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
|
||||
"integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="
|
||||
},
|
||||
"tiny-invariant": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz",
|
||||
@ -17300,6 +17374,15 @@
|
||||
"pathe": "^0.3.0"
|
||||
}
|
||||
},
|
||||
"unicode-trie": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz",
|
||||
"integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==",
|
||||
"requires": {
|
||||
"pako": "^0.2.5",
|
||||
"tiny-inflate": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"unimport": {
|
||||
"version": "0.4.5",
|
||||
"resolved": "https://registry.npmjs.org/unimport/-/unimport-0.4.5.tgz",
|
||||
|
@ -27,6 +27,7 @@
|
||||
"@vueuse/nuxt": "^8.7.5",
|
||||
"chart.js": "^3.8.0",
|
||||
"date-fns": "^2.28.0",
|
||||
"fast-fuzzy": "^1.11.2",
|
||||
"format-duration": "^2.0.0",
|
||||
"html-entities": "^2.3.3",
|
||||
"primeflex": "^3.2.1",
|
||||
|
@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { useLocalStorage } from '@vueuse/core';
|
||||
import { $fetch } from 'ohmyfetch';
|
||||
import AutoComplete from 'primevue/autocomplete';
|
||||
|
||||
@ -6,10 +7,8 @@ import { SearchResult } from '~~/models/search-result';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const searchHistory = useCookie<SearchResult[]>('searchHistory');
|
||||
searchHistory.value = searchHistory.value || [];
|
||||
|
||||
const searchText = ref('');
|
||||
const searchHistory = useLocalStorage('searchHistory', []);
|
||||
const searchName = ref('');
|
||||
const suggestions = ref([]);
|
||||
|
||||
// For some odd reason, if you don't do JSON.parse(JSON.stringify),
|
||||
@ -18,10 +17,10 @@ const displaySearchHistory = () =>
|
||||
(suggestions.value = JSON.parse(JSON.stringify(searchHistory.value)));
|
||||
|
||||
const findPlaylists = async () => {
|
||||
if (searchText.value.length === 0) return displaySearchHistory();
|
||||
if (searchName.value.length === 0) return displaySearchHistory();
|
||||
|
||||
try {
|
||||
const urlObject = new URL(searchText.value);
|
||||
const urlObject = new URL(searchName.value);
|
||||
const [collectionName, playlistId] = urlObject.pathname
|
||||
.split('/')
|
||||
.filter(Boolean);
|
||||
@ -40,7 +39,7 @@ const findPlaylists = async () => {
|
||||
: [
|
||||
{
|
||||
id: playlistId,
|
||||
title: JSON.parse(body).original_name
|
||||
name: JSON.parse(body).original_name
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -54,7 +53,7 @@ const findPlaylists = async () => {
|
||||
if (error.message === 'This is not a valid playlist link') return [];
|
||||
|
||||
const searchResults = await $fetch<SearchResult[]>(
|
||||
`/api/playlists/search?title=${searchText.value}`
|
||||
`/api/playlists/search?name=${searchName.value}`
|
||||
);
|
||||
|
||||
return (suggestions.value = searchResults);
|
||||
@ -81,18 +80,18 @@ const openSnapshotsCalendar = async ({
|
||||
|
||||
<template>
|
||||
<NuxtLayout name="centered-content">
|
||||
<h1 class="m-0 md:text-5xl text-4xl">Spotify Playlist Archive</h1>
|
||||
<h1 class="m-0 md:text-5xl text-3xl">Spotify Playlist Archive</h1>
|
||||
<div class="md:p-0 p-2 flex flex-column justify-content-center text-center">
|
||||
<p class="md:mt-3 mt-2 text-lg text-gray-300">
|
||||
Browse past versions of thousands of playlists saved over time
|
||||
</p>
|
||||
<div class="w-full md:px-0 px-3">
|
||||
<AutoComplete
|
||||
v-model="searchText"
|
||||
v-model="searchName"
|
||||
:suggestions="suggestions"
|
||||
class="w-full"
|
||||
placeholder="Type in at least 3 characters, or paste a playlist link"
|
||||
field="title"
|
||||
placeholder="Start typing, or paste a playlist link"
|
||||
field="name"
|
||||
:min-length="3"
|
||||
complete-on-focus
|
||||
@complete="findPlaylists"
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { $fetch } from 'ohmyfetch';
|
||||
import { Searcher } from 'fast-fuzzy';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const query = useQuery(event);
|
||||
const searchTitle = query.title as string;
|
||||
const searchPhrases = searchTitle.trim().split(/[ ]{1,}/);
|
||||
const searchName = query.name as string;
|
||||
|
||||
if (!searchName || searchName.length < 3) return [];
|
||||
|
||||
const readmeFileContent = await $fetch<string>(
|
||||
'https://raw.githubusercontent.com/mackorone/spotify-playlist-archive/main/README.md'
|
||||
@ -16,15 +18,15 @@ export default defineEventHandler(async (event) => {
|
||||
.replaceAll('.md)', '')
|
||||
.split('\n')
|
||||
.map((textEntry) => {
|
||||
const [title, id] = textEntry.split(' /playlists/pretty/');
|
||||
const [name, id] = textEntry.split(' /playlists/pretty/');
|
||||
|
||||
return { title, id };
|
||||
})
|
||||
.filter((entry) =>
|
||||
searchPhrases.every((phrase) =>
|
||||
entry.title.toLowerCase().includes(phrase.toLowerCase())
|
||||
)
|
||||
);
|
||||
return { name, id };
|
||||
});
|
||||
|
||||
return archiveEntries;
|
||||
const fuzzySearcher = new Searcher(archiveEntries, {
|
||||
keySelector: (obj) => obj.name
|
||||
});
|
||||
const searchResults = fuzzySearcher.search(searchName).slice(0, 10);
|
||||
|
||||
return searchResults;
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user