diff --git a/website-frontend/src/lib/components/banners/PeopleBanner.svelte b/website-frontend/src/lib/components/banners/PeopleBanner.svelte index cd70f4de..3dca6e55 100644 --- a/website-frontend/src/lib/components/banners/PeopleBanner.svelte +++ b/website-frontend/src/lib/components/banners/PeopleBanner.svelte @@ -65,13 +65,13 @@
-
-

{position}

+ + {position} {#if laboratory} -

·

-

{laboratory}

+ · + {laboratory} {/if} -
+
diff --git a/website-frontend/src/lib/components/cards/FeaturedEventCard.svelte b/website-frontend/src/lib/components/cards/FeaturedEventCard.svelte index 3285c581..b0ea8c74 100644 --- a/website-frontend/src/lib/components/cards/FeaturedEventCard.svelte +++ b/website-frontend/src/lib/components/cards/FeaturedEventCard.svelte @@ -2,7 +2,6 @@ import { PUBLIC_APIURL } from '$env/static/public'; import { Event } from '$lib/models/events'; import { Calendar, MapPin, Clock, Image } from 'lucide-svelte'; - import { reloading } from '$lib/stores'; export let item: Event; let hero_height: number; @@ -38,13 +37,7 @@ } - { - $reloading = true; - }} -> +
diff --git a/website-frontend/src/lib/components/cards/NewsCard.svelte b/website-frontend/src/lib/components/cards/NewsCard.svelte index bae4ad10..91435794 100644 --- a/website-frontend/src/lib/components/cards/NewsCard.svelte +++ b/website-frontend/src/lib/components/cards/NewsCard.svelte @@ -3,7 +3,6 @@ import { NewsItem } from '$lib/models/news'; import { Image } from 'lucide-svelte'; import { error } from '@sveltejs/kit'; - import { reloading } from '$lib/stores'; export let item: NewsItem; const news_tags = @@ -17,13 +16,7 @@ let banner_height: number; - { - $reloading = true; - }} -> +
diff --git a/website-frontend/src/lib/components/cards/PublicationCard.svelte b/website-frontend/src/lib/components/cards/PublicationCard.svelte index 99af21fe..b3500cba 100644 --- a/website-frontend/src/lib/components/cards/PublicationCard.svelte +++ b/website-frontend/src/lib/components/cards/PublicationCard.svelte @@ -5,7 +5,6 @@ import { PUBLIC_APIURL } from '$env/static/public'; import { Publication } from '$lib/models/publications'; import { SquareArrowOutUpRight } from 'lucide-svelte'; - import { reloading } from '$lib/stores'; export let item: Publication; @@ -85,33 +84,29 @@
{/if} {#if item.authors} - {#if item.authors.length !== 1} - Authors: - {:else} - Author: - {/if} - {#each item.authors as author, i} - {#if author.link && typeof author.link === 'string'} -
{ - $reloading = true; - }} - >{`${author.last_name}, ${author.first_name}`} - +
+ {#if item.authors.length !== 1} + Authors: {:else} - {`${author.last_name}, ${author.first_name}`} - {/if} - {#if i + 1 !== item.authors.length} - {` & `} + Author: {/if} - {/each} + {#each item.authors as author, i} + {#if author.link && typeof author.link === 'string'} + {`${author.last_name}, ${author.first_name}`} + + {:else} + {`${author.last_name}, ${author.first_name}`} + {/if} + {#if i + 1 !== item.authors.length} + {` & `} + {/if} + {/each} +
{/if}
{#if item.abstract} -
+
Abstract: {item.abstract}
{/if} diff --git a/website-frontend/src/lib/components/list_items/LaboratoryItem.svelte b/website-frontend/src/lib/components/list_items/LaboratoryItem.svelte index e08b2767..377f1c47 100644 --- a/website-frontend/src/lib/components/list_items/LaboratoryItem.svelte +++ b/website-frontend/src/lib/components/list_items/LaboratoryItem.svelte @@ -1,7 +1,6 @@ - { - $reloading = true; - }} -> +
import { Image } from 'lucide-svelte'; import { PUBLIC_APIURL } from '$env/static/public'; - import { reloading } from '$lib/stores'; export let organization; let logo_height: number; @@ -9,13 +8,7 @@ $: ({ name, logo, description, website, slug } = organization); - { - $reloading = true; - }} -> +
/** @type {import('./$types').PageData} */ import { Moon } from 'svelte-loading-spinners'; - import { ChevronsUp, Frown, Plus } from 'lucide-svelte'; + import { Frown, Plus } from 'lucide-svelte'; + import { enhance } from '$app/forms'; import { page } from '$app/stores'; - import { goto } from '$app/navigation'; + import { type ActionResult } from '@sveltejs/kit'; export let onDark = false; - export let layout; - export let data; + export let layout = ''; + export let data: Promise; export let text; export let component; export let count; - export let limit: number; + let items: object[] = []; $: query = new URLSearchParams($page.url.searchParams.toString()); - $: currentLimit = parseInt(query.get('limit') ?? limit.toString()); + $: data.then((res) => (items = res)); - function nav(newLimit: number): void { - query.delete('limit'); - if (newLimit > limit) { - query.append('limit', newLimit.toString()); - } - goto(`?${query.toString()}`, { noScroll: true, keepFocus: true }); - } + const handleLoadMore = ({ formData }: { formData: FormData }) => { + formData.set('data', JSON.stringify(items)); + return async ({ result }: { result: ActionResult }) => { + if (result.type === 'success' && result.data) { + items = result.data.items; + } + }; + };
@@ -38,42 +40,30 @@ Loading {text}...

- {:then data} + {:then}
- {#each data as item} + {#each items as item} {/each}
- {#if data.length < count} - - {/if} - {#if data.length > limit} - + {#if items.length < count} +
+ +
{/if}
Showing {Math.min(currentLimit, count)} items out of {count}Showing {items.length} items out of {count}
{:catch} diff --git a/website-frontend/src/lib/components/search/EventsSearchResult.svelte b/website-frontend/src/lib/components/search/EventsSearchResult.svelte new file mode 100644 index 00000000..e41b8408 --- /dev/null +++ b/website-frontend/src/lib/components/search/EventsSearchResult.svelte @@ -0,0 +1,36 @@ + + +
+
+ {#if item.hero_image} +
+ {#if general_banner_height} + {item.event_headline} + {/if} +
+ {/if} + +
+

{item.event_headline}

+ {#if item.event_content} +

{@html item.event_content}

+ {/if} +
+
+
diff --git a/website-frontend/src/lib/components/search/LaboratoriesSearchResult.svelte b/website-frontend/src/lib/components/search/LaboratoriesSearchResult.svelte new file mode 100644 index 00000000..2779f5b8 --- /dev/null +++ b/website-frontend/src/lib/components/search/LaboratoriesSearchResult.svelte @@ -0,0 +1,36 @@ + + + +
+ {#if item.logo} +
+ {#if general_banner_height} + {item.name} + {/if} +
+ {/if} + +
+

{item.name}

+ {#if item.brief_description} +

{item.brief_description}

+ {/if} +
+
+
diff --git a/website-frontend/src/lib/components/search/NewsSearchResult.svelte b/website-frontend/src/lib/components/search/NewsSearchResult.svelte new file mode 100644 index 00000000..0206e461 --- /dev/null +++ b/website-frontend/src/lib/components/search/NewsSearchResult.svelte @@ -0,0 +1,36 @@ + + + +
+ {#if item.background_image} +
+ {#if general_banner_height} + {item.title} + {/if} +
+ {/if} + +
+

{item.title}

+ {#if item.summary} +

{item.summary}

+ {/if} +
+
+
diff --git a/website-frontend/src/lib/components/search/PeopleSearchResult.svelte b/website-frontend/src/lib/components/search/PeopleSearchResult.svelte new file mode 100644 index 00000000..90edb906 --- /dev/null +++ b/website-frontend/src/lib/components/search/PeopleSearchResult.svelte @@ -0,0 +1,47 @@ + + + +
+ {#if item.profile_image} +
+ {#if general_banner_height} + {`${item.first_name} + {/if} +
+ {/if} + +
+

{`${item.first_name} ${item.last_name}`}

+ {#if item.position} +

+ {`${item.position} ${laboratory ? ` · ${laboratory}` : ``}`} +

+ {/if} +
+
+
diff --git a/website-frontend/src/lib/components/search/PublicationsSearchResult.svelte b/website-frontend/src/lib/components/search/PublicationsSearchResult.svelte new file mode 100644 index 00000000..d3cc5087 --- /dev/null +++ b/website-frontend/src/lib/components/search/PublicationsSearchResult.svelte @@ -0,0 +1,138 @@ + + + + +
+ {#if item.hero_image} +
+ {#if pubs_banner_height} + {item.title} + {/if} +
+ {/if} + +
+

{item.title}

+ {#if item.abstract} +

+ {item.abstract} +

+ {/if} +
+
+
+ + + {item.title} + + {#if publications_tags.length !== 0} +
+ + {#each publications_tags.slice(0, -1) as publications_tag} + {`${publications_tag}, `} + {/each} + {publications_tags.at(-1)} + +
+ {/if} +
+ {#if item.publish_date} +
+ Date published: + {new Date(item.publish_date).toLocaleDateString('en-EN', { + year: 'numeric', + month: 'long', + day: 'numeric' + })} +
+ {/if} + {#if item.authors} +
+ {#if item.authors.length !== 1} + Authors: + {:else} + Author: + {/if} + {#each item.authors as author, i} + {#if author.link && typeof author.link === 'string'} + {`${author.last_name}, ${author.first_name}`} + + {:else} + {`${author.last_name}, ${author.first_name}`} + {/if} + {#if i + 1 !== item.authors.length} + {` & `} + {/if} + {/each} +
+ {/if} +
+ {#if item.abstract} +
+ Abstract: {item.abstract} +
+ {/if} + {#if item.access_links} +
+ + {#if item.access_links.length !== 1} + Access Links: + {:else} + Access Link: + {/if} + {#each item.access_links as access_link, i} + + {/each} + +
+ {/if} +
+
+
+
diff --git a/website-frontend/src/lib/components/search/SearchResult.svelte b/website-frontend/src/lib/components/search/SearchResult.svelte deleted file mode 100644 index eac8f919..00000000 --- a/website-frontend/src/lib/components/search/SearchResult.svelte +++ /dev/null @@ -1,167 +0,0 @@ - - -{#if !publication} - -
-
- {#if image && general_banner_height} - {name} - {/if} -
- -
- -
-
-
-{:else} - - -
-
- {#if image && pubs_banner_height} - {name} - {/if} -
- -
- -
-
-
- - - {publication.title} - - {#if publications_tags.length !== 0} -
- - {#each publications_tags.slice(0, -1) as publications_tag} - {`${publications_tag}, `} - {/each} - {publications_tags.at(-1)} - -
- {/if} -
- {#if publication.publish_date} -
- Date published: - {new Date(publication.publish_date).toLocaleDateString('en-EN', { - year: 'numeric', - month: 'long', - day: 'numeric' - })} -
- {/if} - {#if publication.authors} - {#if publication.authors.length !== 1} - Authors: - {:else} - Author: - {/if} - {#each publication.authors as author, i} - {#if author.link && typeof author.link === 'string'} - { - $reloading = true; - }} - >{`${author.last_name}, ${author.first_name}`} - - {:else} - {`${author.last_name}, ${author.first_name}`} - {/if} - {#if i + 1 !== publication.authors.length} - {` & `} - {/if} - {/each} - {/if} -
- {#if publication.abstract} -
- Abstract: {publication.abstract} -
- {/if} - {#if publication.access_links} -
- - {#if publication.access_links.length !== 1} - Access Links: - {:else} - Access Link: - {/if} - {#each publication.access_links as access_link, i} - - {/each} - -
- {/if} -
-
-
-
-{/if} diff --git a/website-frontend/src/lib/models/junctions/people_related.ts b/website-frontend/src/lib/models/junctions/people_related.ts index 6066df35..487dee59 100644 --- a/website-frontend/src/lib/models/junctions/people_related.ts +++ b/website-frontend/src/lib/models/junctions/people_related.ts @@ -1,17 +1,17 @@ -import { array, lazy, object, partial, string, union, type GenericSchema } from 'valibot'; +import { array, lazy, number, object, partial, string, union, type GenericSchema } from 'valibot'; import { Person } from '../people'; import { Level } from '../people_levels'; export type PeopleRelated = { people_id?: string | Person; - people_levels_id?: string | Level; + people_levels_id?: number | Level; }[]; export const PeopleRelated: GenericSchema = array( partial( object({ people_id: union([string(), lazy(() => Person)]), - people_levels_id: union([string(), lazy(() => Level)]) + people_levels_id: union([number(), lazy(() => Level)]) }) ) ); diff --git a/website-frontend/src/lib/models/people.ts b/website-frontend/src/lib/models/people.ts index dbd69042..33e023f2 100644 --- a/website-frontend/src/lib/models/people.ts +++ b/website-frontend/src/lib/models/people.ts @@ -58,4 +58,4 @@ export const People = array(Person); export type EducationalAttainment = InferOutput; export type Person = InferOutput; -export type Peoples = InferOutput; +export type People = InferOutput; diff --git a/website-frontend/src/lib/stores.ts b/website-frontend/src/lib/stores.ts index b44e8b3d..c75d3d65 100644 --- a/website-frontend/src/lib/stores.ts +++ b/website-frontend/src/lib/stores.ts @@ -1,6 +1,5 @@ import { writable } from 'svelte/store'; -export const reloading = writable(false); export const searchOpen = writable(false); export const mobileOpen = writable(false); export const searchInput = writable(''); diff --git a/website-frontend/src/routes/+error.svelte b/website-frontend/src/routes/+error.svelte index 1fd65936..7f919232 100644 --- a/website-frontend/src/routes/+error.svelte +++ b/website-frontend/src/routes/+error.svelte @@ -2,7 +2,7 @@ import { page } from '$app/stores'; -
+

{$page.status}: {$page.error?.message}

diff --git a/website-frontend/src/routes/news/+page.ts b/website-frontend/src/routes/news/+page.ts index 110d7348..1114c213 100644 --- a/website-frontend/src/routes/news/+page.ts +++ b/website-frontend/src/routes/news/+page.ts @@ -2,7 +2,6 @@ export async function load({ data }) { return { news_overview: data.news_overview, - news_limit: data.news_limit, news_count: data.news_count, news: data.news }; diff --git a/website-frontend/src/routes/news/[slug]/+page.server.ts b/website-frontend/src/routes/news/[slug]/+page.server.ts index ac5a0318..7c55c5c4 100644 --- a/website-frontend/src/routes/news/[slug]/+page.server.ts +++ b/website-frontend/src/routes/news/[slug]/+page.server.ts @@ -1,9 +1,12 @@ /** @type {import('./$types').PageServerLoad} */ import { aggregate, readItems } from '@directus/sdk'; import getDirectusInstance from '$lib/directus'; -import { error, redirect } from '@sveltejs/kit'; -import { parse } from 'valibot'; +import { error } from '@sveltejs/kit'; +import { awaitAsync, parse, parseAsync, pipeAsync, promise } from 'valibot'; import { News, NewsItem } from '$lib/models/news'; +import type { Actions } from './$types'; + +const limit = 12; export async function load({ url, params, fetch }) { const directus = getDirectusInstance(fetch); @@ -43,7 +46,6 @@ export async function load({ url, params, fetch }) { const news_item = parse(NewsItem, news[0]); - const other_news_limit = 12; const other_news_count = await directus .request( aggregate('news', { @@ -60,11 +62,9 @@ export async function load({ url, params, fetch }) { }) ) .then((res) => res[0].count); - if (parseInt(url.searchParams.get('limit') ?? other_news_limit.toString()) < other_news_limit) - throw redirect(302, url.pathname); - const other_news = parse( - News, - await directus.request( + const other_news = parseAsync( + pipeAsync(promise(), awaitAsync(), News), + directus.request( readItems('news', { fields: [ '*', @@ -88,12 +88,57 @@ export async function load({ url, params, fetch }) { } }, sort: ['-date_created'], - limit: Math.max(other_news_limit, parseInt(url.searchParams.get('limit') ?? '0')) + limit }) ) ); const link = new URL(url.toString()).toString(); - return { link, other_news_limit, other_news_count, other_news, news_item }; + return { link, other_news_count, other_news, news_item }; } + +export const actions = { + loadMore: async ({ request, params, fetch }) => { + const data = await request.formData(); + const current = JSON.parse(data.get('data') as string); + const directus = getDirectusInstance(fetch); + const other_news = parse( + News, + await directus.request( + readItems('news', { + fields: [ + '*', + { + user_created: ['first_name', 'last_name'] + }, + { + user_updated: ['first_name', 'last_name'] + }, + { + news_tags: [ + { + news_tags_id: ['name'] + } + ] + } + ], + filter: { + slug: { + _neq: params.slug + } + }, + sort: ['-date_created'], + offset: current.length, + limit + }) + ) + ); + + const items = [...current, ...other_news]; + return { + success: true, + items + }; + } +} satisfies Actions; diff --git a/website-frontend/src/routes/news/[slug]/+page.svelte b/website-frontend/src/routes/news/[slug]/+page.svelte index c9d080e5..81336967 100644 --- a/website-frontend/src/routes/news/[slug]/+page.svelte +++ b/website-frontend/src/routes/news/[slug]/+page.svelte @@ -10,7 +10,7 @@ export let data; let banner_height: number; - $: ({ link, other_news_limit, other_news_count, other_news, news_item } = data); + $: ({ link, other_news_count, other_news, news_item } = data); $: news_tags = news_item.news_tags ? news_item.news_tags @@ -110,7 +110,6 @@ text="other news" component={NewsCard} count={other_news_count} - limit={other_news_limit} />
diff --git a/website-frontend/src/routes/research/publications/+page.server.ts b/website-frontend/src/routes/research/publications/+page.server.ts index af04e2f8..30da9f49 100644 --- a/website-frontend/src/routes/research/publications/+page.server.ts +++ b/website-frontend/src/routes/research/publications/+page.server.ts @@ -1,11 +1,14 @@ /** @type {import('./$types').PageServerLoad} */ -import { readItem, readItems, readSingleton } from '@directus/sdk'; +import { aggregate, readItems, readSingleton } from '@directus/sdk'; import getDirectusInstance from '$lib/directus'; -import { parse } from 'valibot'; +import { awaitAsync, parse, parseAsync, pipeAsync, promise } from 'valibot'; import { Publications } from '$lib/models/publications'; import { Laboratories } from '$lib/models/laboratories'; import { PublicationsTags } from '$lib/models/publications_tags.js'; import { Research } from '$lib/models/research'; +import type { Actions } from '@sveltejs/kit'; + +const limit = 12; export async function load({ fetch, url }) { const directus = getDirectusInstance(fetch); @@ -18,6 +21,10 @@ export async function load({ fetch, url }) { tags: url.searchParams.getAll('tags') }; + const people = new Map( + (await directus.request(readItems('people'))).map((person) => [person.id, person]) + ); + const years_filters = await (async () => { const res = parse( Publications, @@ -60,10 +67,31 @@ export async function load({ fetch, url }) { ) ).map(({ name }) => name); - const publications = await (async () => { - const res = parse( - Publications, - await directus.request( + const publications_count = await directus + .request( + aggregate('publications', { + aggregate: { count: '*' }, + query: { + filter: { + 'year(publish_date)': { _in: filters.years.length !== 0 ? filters.years : undefined }, + laboratory: { + name: { _in: filters.laboratories.length !== 0 ? filters.laboratories : undefined } + }, + publication_tags: { + publications_tags_id: { + name: { _in: filters.tags.length !== 0 ? filters.tags : undefined } + } + } + } + } + }) + ) + .then((res) => parseInt(res[0].count ?? '0')); + + const publications = parseAsync( + pipeAsync(promise(), awaitAsync(), Publications), + directus + .request( readItems('publications', { fields: [ '*', @@ -86,33 +114,131 @@ export async function load({ fetch, url }) { } } }, - sort: ['-publish_date'] + sort: ['-publish_date'], + limit }) ) - ); - - return await Promise.all( - res.map(async (item) => { - return await { - ...item, - authors: await Promise.all( - item.authors - ? item.authors.map(async (author) => { - if (typeof author.link === 'undefined') return author; - if (typeof author.link === 'object') { - const person = await directus.request(readItem('people', author.link.key)); - return { - ...author, - link: `/people/${person.username}` - }; - } - }) - : [] + .then( + async (res) => + await Promise.all( + res.map(async (item) => { + return { + ...item, + authors: await Promise.all( + item.authors + ? item.authors.map(async (author) => { + if (typeof author.link === 'undefined') return author; + if (typeof author.link === 'object') { + const person = people.get(author.link.key); + return { + ...author, + link: `/people/${person ? (person.username ?? '') : ''}` + }; + } + }) + : [] + ) + }; + }) ) - }; - }) - ); - })(); + ) + ); - return { research, publications, years_filters, laboratories_filters, tags_filters }; + return { + research, + years_filters, + laboratories_filters, + tags_filters, + publications_count, + publications + }; } + +export const actions = { + loadMore: async ({ request, fetch, url }) => { + const data = await request.formData(); + const current = JSON.parse(data.get('data') as string); + const directus = getDirectusInstance(fetch); + const filters = { + years: url.searchParams.getAll('year'), + laboratories: url.searchParams.getAll('laboratory'), + tags: url.searchParams.getAll('tags') + }; + const people = new Map( + (await directus.request(readItems('people'))).map((person) => [person.id, person]) + ); + const publications = parse( + Publications, + await directus + .request( + readItems('publications', { + fields: [ + '*', + { + publication_tags: [ + { + publications_tags_id: ['name'] + } + ] + } + ], + filter: { + 'year(publish_date)': { _in: filters.years.length !== 0 ? filters.years : undefined }, + laboratory: { + name: { _in: filters.laboratories.length !== 0 ? filters.laboratories : undefined } + }, + publication_tags: { + publications_tags_id: { + name: { _in: filters.tags.length !== 0 ? filters.tags : undefined } + } + } + }, + sort: ['-publish_date'], + offset: current.length, + limit + }) + ) + .then( + async (res) => + await Promise.all( + res.map(async (item) => { + return { + ...item, + authors: await Promise.all( + item.authors + ? item.authors.map(async (author) => { + if (typeof author.link === 'undefined') return author; + if (typeof author.link === 'object') { + const person = people.get(author.link.key); + return { + ...author, + link: `/people/${person ? (person.username ?? '') : ''}` + }; + } + }) + : [] + ) + }; + }) + ) + ) + ); + const items = [...current, ...publications].sort((a, b) => { + if ((url.searchParams.get('sort') ?? '') === 'author') { + const aLastName = a.authors[0]?.last_name || ''; + const bLastName = b.authors[0]?.last_name || ''; + return aLastName.localeCompare(bLastName); + } else if ((url.searchParams.get('sort') ?? '') === 'title') { + const a_title = a.title || ''; + const b_title = b.title || ''; + return a_title.localeCompare(b_title); + } else { + return (b.publish_date ?? '1970-01-01').localeCompare(a.publish_date ?? '1970-01-01'); + } + }); + return { + success: true, + items + }; + } +} satisfies Actions; diff --git a/website-frontend/src/routes/research/publications/+page.svelte b/website-frontend/src/routes/research/publications/+page.svelte index aee27199..c97b9ace 100644 --- a/website-frontend/src/routes/research/publications/+page.svelte +++ b/website-frontend/src/routes/research/publications/+page.svelte @@ -1,20 +1,26 @@ @@ -62,16 +52,25 @@ -
- {#each publicationsList as publication} - - {/each} -
- {#if shown < publications.length} -
- -
- {/if} + + res.sort((a, b) => { + if (($page.url.searchParams.get('sort') ?? '') === 'author') { + const aLastName = a.authors ? (a.authors[0].last_name ?? '') : ''; + const bLastName = b.authors ? (b.authors[0].last_name ?? '') : ''; + return aLastName.localeCompare(bLastName); + } else if (($page.url.searchParams.get('sort') ?? '') === 'title') { + const a_title = a.title || ''; + const b_title = b.title || ''; + return a_title.localeCompare(b_title); + } else { + return (b.publish_date ?? '1970-01-01').localeCompare(a.publish_date ?? '1970-01-01'); + } + }) + )} + text="publications" + component={PublicationCard} + count={publications_count} + />
diff --git a/website-frontend/src/routes/research/publications/+page.ts b/website-frontend/src/routes/research/publications/+page.ts index c703b3fb..542c24c8 100644 --- a/website-frontend/src/routes/research/publications/+page.ts +++ b/website-frontend/src/routes/research/publications/+page.ts @@ -2,9 +2,10 @@ export async function load({ data }) { return { research: data.research, - publications: data.publications, years_filters: data.years_filters, laboratories_filters: data.laboratories_filters, - tags_filters: data.tags_filters + tags_filters: data.tags_filters, + publications_count: data.publications_count, + publications: data.publications }; } diff --git a/website-frontend/src/routes/search/+page.server.ts b/website-frontend/src/routes/search/+page.server.ts index 6c6c6c0e..a1cb2fff 100644 --- a/website-frontend/src/routes/search/+page.server.ts +++ b/website-frontend/src/routes/search/+page.server.ts @@ -1,53 +1,113 @@ /** @type {import('./$types').PageServerLoad} */ -import { readItem, readItems } from '@directus/sdk'; +import { aggregate, readItems } from '@directus/sdk'; import getDirectusInstance from '$lib/directus'; -import { parse } from 'valibot'; -import { News } from '$lib/models/news'; -import { Events } from '$lib/models/events'; -import { People } from '$lib/models/people'; -import { Laboratories } from '$lib/models/laboratories'; -import { Publications } from '$lib/models/publications'; +import { awaitAsync, parse, parseAsync, pipeAsync, promise } from 'valibot'; +import { News, NewsItem } from '$lib/models/news'; +import { Events, Event } from '$lib/models/events'; +import { People, Person } from '$lib/models/people'; +import { Laboratories, Laboratory } from '$lib/models/laboratories'; +import { Publication, Publications } from '$lib/models/publications'; +import type { Actions } from '@sveltejs/kit'; + +const limit = 6; export async function load({ fetch, url }) { const directus = getDirectusInstance(fetch); - const news = parse( - News, - await directus.request( + const news_count = await directus + .request( + aggregate('news', { + aggregate: { count: '*' }, + query: { + search: url.searchParams.get('q') ?? undefined + } + }) + ) + .then((res) => parseInt(res[0].count ?? '0')); + const news = parseAsync( + pipeAsync(promise(), awaitAsync(), News), + directus.request( readItems('news', { - search: url.searchParams.get('q') ?? undefined + search: url.searchParams.get('q') ?? undefined, + limit }) ) ); - const events = parse( - Events, - await directus.request( + const events_count = await directus + .request( + aggregate('events', { + aggregate: { count: '*' }, + query: { + search: url.searchParams.get('q') ?? undefined + } + }) + ) + .then((res) => parseInt(res[0].count ?? '0')); + const events = parseAsync( + pipeAsync(promise(), awaitAsync(), Events), + directus.request( readItems('events', { - search: url.searchParams.get('q') ?? undefined + search: url.searchParams.get('q') ?? undefined, + limit }) ) ); - const people = parse( - People, - await directus.request( + const people_count = await directus + .request( + aggregate('people', { + aggregate: { count: '*' }, + query: { + search: url.searchParams.get('q') ?? undefined + } + }) + ) + .then((res) => parseInt(res[0].count ?? '0')); + const people = parseAsync( + pipeAsync(promise(), awaitAsync(), People), + directus.request( readItems('people', { - search: url.searchParams.get('q') ?? undefined + search: url.searchParams.get('q') ?? undefined, + limit }) ) ); - const laboratories = parse( - Laboratories, - await directus.request( + const laboratories_count = await directus + .request( + aggregate('laboratories', { + aggregate: { count: '*' }, + query: { + search: url.searchParams.get('q') ?? undefined + } + }) + ) + .then((res) => parseInt(res[0].count ?? '0')); + const laboratories = parseAsync( + pipeAsync(promise(), awaitAsync(), Laboratories), + directus.request( readItems('laboratories', { - search: url.searchParams.get('q') ?? undefined + search: url.searchParams.get('q') ?? undefined, + limit }) ) ); - const publications = await (async () => { - const publications = parse( - Publications, - await directus.request( + const publications_count = await directus + .request( + aggregate('publications', { + aggregate: { count: '*' }, + query: { + search: url.searchParams.get('q') ?? undefined + } + }) + ) + .then((res) => parseInt(res[0].count ?? '0')); + const peopleMap = new Map((await people).map((person) => [person.id, person])); + const publications = parseAsync( + pipeAsync(promise(), awaitAsync(), Publications), + directus + .request( readItems('publications', { search: url.searchParams.get('q') ?? undefined, + sort: ['-publish_date'], + limit, fields: [ '*', { @@ -60,32 +120,38 @@ export async function load({ fetch, url }) { ] }) ) - ); - - return await Promise.all( - publications.map(async (item) => { - return await { - ...item, - authors: await Promise.all( - item.authors - ? item.authors.map(async (author) => { - if (typeof author.link === 'undefined') return author; - if (typeof author.link === 'object') { - const person = await directus.request(readItem('people', author.link.key)); - return { - ...author, - link: `/people/${person.username}` - }; - } - }) - : [] + .then( + async (res) => + await Promise.all( + res.map(async (item) => { + return { + ...item, + authors: await Promise.all( + item.authors + ? item.authors.map(async (author) => { + if (typeof author.link === 'undefined') return author; + if (typeof author.link === 'object') { + const person = peopleMap.get(author.link.key); + return { + ...author, + link: `/people/${person ? (person.username ?? '') : ''}` + }; + } + }) + : [] + ) + }; + }) ) - }; - }) - ); - })(); + ) + ); return { + news_count, + events_count, + people_count, + laboratories_count, + publications_count, news, events, people, @@ -93,3 +159,127 @@ export async function load({ fetch, url }) { publications }; } + +export const actions = { + loadMore: async ({ request, fetch, url }) => { + const tab = url.searchParams.get('tab') ?? 'News'; + const data = await request.formData(); + const current = JSON.parse(data.get('data') as string); + const directus = getDirectusInstance(fetch); + let news: News = []; + let events: Events = []; + let people: People = []; + let laboratories: Laboratories = []; + let publications: Publications = []; + let items: (NewsItem | Event | Person | Laboratory | Publication)[] = []; + if (tab == 'News') { + news = parse( + News, + await directus.request( + readItems('news', { + search: url.searchParams.get('q') ?? undefined, + offset: current.length, + limit + }) + ) + ); + items = [...current, ...news]; + } + if (tab == 'Events') { + events = parse( + Events, + await directus.request( + readItems('events', { + search: url.searchParams.get('q') ?? undefined, + offset: current.length, + limit + }) + ) + ); + items = [...current, ...events]; + } + if (tab == 'People') { + people = parse( + People, + await directus.request( + readItems('people', { + search: url.searchParams.get('q') ?? undefined, + offset: current.length, + limit + }) + ) + ); + items = [...current, ...people]; + } + if (tab == 'Laboratories') { + laboratories = parse( + Laboratories, + await directus.request( + readItems('laboratories', { + search: url.searchParams.get('q') ?? undefined, + offset: current.length, + limit + }) + ) + ); + items = [...current, ...laboratories]; + } + if (tab == 'Publications') { + const people = new Map( + (await directus.request(readItems('people'))).map((person) => [person.id, person]) + ); + publications = parse( + Publications, + await directus + .request( + readItems('publications', { + search: url.searchParams.get('q') ?? undefined, + sort: ['-publish_date'], + offset: current.length, + limit, + fields: [ + '*', + { + publication_tags: [ + { + publications_tags_id: ['name'] + } + ] + } + ] + }) + ) + .then( + async (res) => + await Promise.all( + res.map(async (item) => { + return { + ...item, + authors: await Promise.all( + item.authors + ? item.authors.map(async (author) => { + if (typeof author.link === 'undefined') return author; + if (typeof author.link === 'object') { + const person = people.get(author.link.key); + return { + ...author, + link: `/people/${person ? (person.username ?? '') : ''}` + }; + } + }) + : [] + ) + }; + }) + ) + ) + ); + items = [...current, ...publications]; + } + + return { + success: true, + items + }; + } +} satisfies Actions; diff --git a/website-frontend/src/routes/search/+page.svelte b/website-frontend/src/routes/search/+page.svelte index d72e9b40..ca39b664 100644 --- a/website-frontend/src/routes/search/+page.svelte +++ b/website-frontend/src/routes/search/+page.svelte @@ -3,56 +3,94 @@ import * as Tabs from '$lib/@shadcn-svelte/ui/tabs'; import { Search } from 'lucide-svelte'; - import SearchResult from '$lib/components/search/SearchResult.svelte'; - import { ScrollArea } from '$lib/@shadcn-svelte/ui/scroll-area/index.js'; import { page } from '$app/stores'; + import Await from '$lib/components/loading/Await.svelte'; + import { goto } from '$app/navigation'; + import NewsSearchResult from '$lib/components/search/NewsSearchResult.svelte'; + import EventsSearchResult from '$lib/components/search/EventsSearchResult.svelte'; + import PeopleSearchResult from '$lib/components/search/PeopleSearchResult.svelte'; + import LaboratoriesSearchResult from '$lib/components/search/LaboratoriesSearchResult.svelte'; + import PublicationsSearchResult from '$lib/components/search/PublicationsSearchResult.svelte'; export let data; - let tabs; + const layout = 'lg:w-3/4'; - $: ({ news, events, people, laboratories, publications } = data); + $: ({ + news_count, + events_count, + people_count, + laboratories_count, + publications_count, + news, + events, + people, + laboratories, + publications + } = data); + $: query = new URLSearchParams($page.url.searchParams.toString()); + $: value = + query.get('tab') ?? + (news_count > 0 + ? 'News' + : events_count > 0 + ? 'Events' + : people_count > 0 + ? 'People' + : laboratories_count > 0 + ? 'Laboratories' + : publications_count > 0 + ? 'Publications' + : 'News'); $: tabs = [ - { - tab: 'All', - res: news.length + events.length + people.length + laboratories.length + publications.length - }, + // { + // tab: 'All', + // res: news_count + events_count + people_count + laboratories_count + publications_count + // }, { tab: 'News', - res: news.length + res: news_count }, { tab: 'Events', - res: events.length + res: events_count }, { tab: 'People', - res: people.length + res: people_count }, { tab: 'Laboratories', - res: laboratories.length + res: laboratories_count }, { tab: 'Publications', - res: publications.length + res: publications_count } ]; -
- - - - {#each tabs as { tab, res }} - {tab} ({res}) - {/each} - - +
+ { + // if (value == 'All') query.delete('tab'); + // else query.set('tab', value ?? ''); + query.set('tab', value ?? ''); + goto(`?${query.toString()}`, { noScroll: true }); + }} + > + + {#each tabs as { tab, res }} + {tab} ({res}) + {/each} + {#each tabs as { tab, res }} @@ -61,130 +99,87 @@

{res} Search result{#if res !== 1}s{/if} for {$page.url.searchParams.get('q')} - {#if tab !== 'All'} + in {tab} +

- - {#if tab === 'All'} - {#each news as { slug, title, summary, background_image }} - -

{title}

- {#if summary} -

{@html summary}

- {/if} -
- {/each} - {#each events as { slug, event_headline, event_content, hero_image }} - -

{event_headline}

- {#if event_content} -

{@html event_content}

- {/if} -
- {/each} - {#each people as { username, first_name, last_name, profile_image, position }} - -

{first_name} {last_name}

- {#if position} -

{position}

- {/if} -
- {/each} - {#each laboratories as { slug, name, description, logo }} - -

{name}

- {#if description} -

{description}

- {/if} -
- {/each} - {#each publications as publication} - -

{publication.title}

- {#if publication.authors.length > 0} -

- {#each publication.authors as { last_name }, i} - {last_name}{#if i + 1 !== publication.authors.length}{`, `}{/if} - {/each} -

- {/if} - {#if publication.abstract} -

{publication.abstract}

- {/if} -
- {/each} - {:else if tab === 'News'} - {#each news as { slug, title, summary, background_image }} - -

{title}

- {#if summary} -

{@html summary}

- {/if} -
- {/each} - {:else if tab === 'Events'} - {#each events as { slug, event_headline, event_content, hero_image }} - -

{event_headline}

- {#if event_content} -

{@html event_content}

- {/if} -
- {/each} - {:else if tab === 'People'} - {#each people as { username, first_name, last_name, profile_image, position }} - -

{first_name} {last_name}

- {#if position} -

{position}

- {/if} -
- {/each} - {:else if tab === 'Laboratories'} - {#each laboratories as { slug, name, description, logo }} - -

{name}

- {#if description} -

{description}

- {/if} -
- {/each} - {:else if tab === 'Publications'} - {#each publications as publication} - -

{publication.title}

- {#if publication.authors.length > 0} -

- {#each publication.authors as { last_name }, i} - {last_name}{#if i + 1 !== publication.authors.length}{`, `}{/if} - {/each} -

- {/if} - {#if publication.abstract} -

{publication.abstract}

- {/if} -
- {/each} + + {#if tab === 'News'} + + {/if} + {#if tab === 'Events'} + + {/if} + {#if tab === 'People'} + + {/if} + {#if tab === 'Laboratories'} + + {/if} + {#if tab === 'Publications'} + {/if} {:else}
@@ -192,12 +187,13 @@
-

+

{#if $page.url.searchParams.get('q')} No search results for {$page.url.searchParams.get('q')} - {#if tab !== 'All'} + in {tab} + {:else} Please provide a search input {/if} diff --git a/website-frontend/src/routes/search/+page.ts b/website-frontend/src/routes/search/+page.ts index a7ad3ae8..3ce1e056 100644 --- a/website-frontend/src/routes/search/+page.ts +++ b/website-frontend/src/routes/search/+page.ts @@ -1,6 +1,11 @@ /** @type {import('./$types').PageLoad} */ export async function load({ data }) { return { + news_count: data.news_count, + events_count: data.events_count, + people_count: data.people_count, + laboratories_count: data.laboratories_count, + publications_count: data.publications_count, news: data.news, events: data.events, people: data.people,