Implement playlist search typeahead

This commit is contained in:
Maciej Pędzich 2022-07-19 22:21:13 +02:00
parent 2a8207868a
commit 8faaf6bed1
3 changed files with 103 additions and 31 deletions

4
models/search-result.ts Normal file
View File

@ -0,0 +1,4 @@
export interface SearchResult {
id: string;
title: string;
}

View File

@ -1,25 +1,63 @@
<script setup lang="ts"> <script setup lang="ts">
import InputText from 'primevue/inputtext'; import { $fetch } from 'ohmyfetch';
import Button from 'primevue/button'; import AutoComplete from 'primevue/autocomplete';
import { SearchResult } from '~~/models/search-result';
const router = useRouter(); const router = useRouter();
const playlistLink = ref('');
const openSnapshotCalendar = async () => { const searchText = ref('');
const urlObject = new URL(playlistLink.value); const suggestions = ref([]);
const params = urlObject.pathname.split('/').filter((p) => p);
const [collectionName, itemId] = params;
if ( const findPlaylists = async () => {
urlObject.hostname === 'open.spotify.com' && try {
collectionName === 'playlist' && const urlObject = new URL(searchText.value);
itemId const [collectionName, playlistId] = urlObject.pathname
) { .split('/')
await router.push(`/playlists/${itemId}/snapshots`); .filter(Boolean);
} else {
throw new Error('This is not a valid playlist link'); if (
urlObject.hostname === 'open.spotify.com' &&
collectionName === 'playlist' &&
playlistId
) {
const searchResults = await $fetch(
`https://raw.githubusercontent.com/mackorone/spotify-playlist-archive/main/playlists/pretty/${playlistId}.json`,
{
parseResponse: (body) =>
body === '404: Not Found'
? []
: [
{
id: playlistId,
title: JSON.parse(body).original_name
}
]
}
).catch((e) => e.data);
suggestions.value = searchResults;
} else {
throw new Error('This is not a valid playlist link');
}
} catch (error) {
if (error.message === 'This is not a valid playlist link') return [];
const searchResults = await $fetch<SearchResult[]>(
`/api/playlists/search?title=${searchText.value}`
);
suggestions.value = searchResults;
} }
}; };
const openSnapshotsCalendar = async ({
value: entry
}: {
value: SearchResult;
}) => {
await router.push(`/playlists/${entry.id}/snapshots`);
};
</script> </script>
<template> <template>
@ -29,23 +67,29 @@ const openSnapshotCalendar = async () => {
<p class="text-xl text-gray-300"> <p class="text-xl text-gray-300">
Browse past versions of thousands of playlists saved over time Browse past versions of thousands of playlists saved over time
</p> </p>
<form <div class="w-full">
class="w-full" <AutoComplete
@submit.prevent="openSnapshotCalendar" v-model="searchText"
@keypress.enter="openSnapshotCalendar" :suggestions="suggestions"
> class="w-full"
<InputText placeholder="Type in at least 3 characters, or paste a playlist link"
v-model="playlistLink" field="title"
type="url" :min-length="3"
class="w-full text-center mt-1 mb-3" @complete="findPlaylists"
placeholder="Enter a playlist link" @item-select="openSnapshotsCalendar"
/> />
<Button </div>
type="submit"
label="Browse snapshots"
:disabled="playlistLink.length === 0"
/>
</form>
</div> </div>
</NuxtLayout> </NuxtLayout>
</template> </template>
<style scoped>
:deep(.p-autocomplete-input) {
width: 100%;
text-align: center;
}
:deep(.p-autocomplete-input::placeholder) {
text-align: center;
}
</style>

View File

@ -0,0 +1,24 @@
import { $fetch } from 'ohmyfetch';
export default defineEventHandler(async (event) => {
const query = useQuery(event);
const readmeFileContent = await $fetch<string>(
'https://raw.githubusercontent.com/mackorone/spotify-playlist-archive/main/README.md'
);
const [, playlistLinksMdList] = readmeFileContent.split('## Playlists\n\n');
const archiveEntries = playlistLinksMdList
.replaceAll('- [', '')
.replaceAll('\\', '')
.replaceAll('](', ' ')
.replaceAll('.md)', '')
.split('\n')
.map((textEntry) => {
const [title, id] = textEntry.split(' /playlists/pretty/');
return { title, id };
})
.filter((entry) => entry.title.includes(query.title as string));
return archiveEntries;
});