diff --git a/src/components/Search/OcResourceSearchSimple/OcResourceSearchSimple.stories.ts b/src/components/Search/OcResourceSearchSimple/OcResourceSearchSimple.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..837a07d94016f68196668726059d061a806bedf3 --- /dev/null +++ b/src/components/Search/OcResourceSearchSimple/OcResourceSearchSimple.stories.ts @@ -0,0 +1,47 @@ +import type { Meta, StoryObj } from '@storybook/vue3' +import { fn } from '@storybook/test' + +import OcResourceSearchSimple from './OcResourceSearchSimple.vue' + +const meta: Meta<typeof OcResourceSearchSimple> = { + component: OcResourceSearchSimple +} + +export default meta +type Story = StoryObj<typeof OcResourceSearchSimple> + +export const Default: Story = { + render: (args) => ({ + components: { OcResourceSearchSimple }, + setup() { + return { args } + }, + template: '<OcResourceSearchSimple v-bind="args" />' + }), + args: { + modelValue: { + params: {} + }, + onSubmit: fn() + } +} + +export const InitialValues: Story = { + render: (args) => ({ + components: { OcResourceSearchSimple }, + setup() { + return { args } + }, + template: '<OcResourceSearchSimple v-bind="args" />' + }), + args: { + modelValue: { + q: "DRIIHM", + params: { + title: ["mon titre"], + creator: ["jean", "michel"] + } + }, + onSubmit: fn() + } +} \ No newline at end of file diff --git a/src/components/Search/OcResourceSearchSimple/OcResourceSearchSimple.vue b/src/components/Search/OcResourceSearchSimple/OcResourceSearchSimple.vue new file mode 100644 index 0000000000000000000000000000000000000000..0a4342642d7e8df9467cc91cf61a09b8b6e340ea --- /dev/null +++ b/src/components/Search/OcResourceSearchSimple/OcResourceSearchSimple.vue @@ -0,0 +1,93 @@ +<template> + <IconField class="w-full mt-8 mb-4"> + <InputIcon class="fa-solid fa-magnifying-glass cursor-pointer hover:text-primary" @click="emit('submit')" /> + <InputText + id="search" + v-model="model.q" + fluid + size="large" + :placeholder="t('community.homepage.searchBarPlaceholder')" + v-on:keyup.enter="emit('submit')" + :disabled="props.loading" + /> + <InputIcon + v-if="searchPop" + :class="{ + 'fa-chevron-down': !searchPop.visible, + 'fa-chevron-up': searchPop.visible + }" + class="fa-solid" + @click="searchPop.toggle" + /> + </IconField> + <div id="popoverPlace" class="h-0 m-0 p-0" /> + <Popover id="popover" class="h-fit w-full" ref="searchPop" appendTo="#popoverPlace"> + <div v-for="key in Object.keys(SearchQueryParams)" v-bind:key="key"> + <label :for="key" class="font-medium">{{ translateValue(searchMetadata[key as keyof typeof SearchQueryParams].label) }}</label> + <OcMultipleField + class="mt-1 mb-2" + :name="key" + :id="key" + :inputComponent="InputText" + :inputProps="{ fluid: true }" + :initialValue="model.params[key as keyof typeof SearchQueryParams]" + v-on:updateValue="handleChange($event, key)" + /> + </div> + <div class="text-right"> + <Button class="mt-6" @click="searchPop.toggle($event), emit('submit')"> + {{ t('search.launchSearch') }} + </Button> + </div> + </Popover> +</template> + +<script setup lang="ts"> +import IconField from 'primevue/iconfield' +import InputText from 'primevue/inputtext' +import InputIcon from 'primevue/inputicon' +import Button from 'primevue/button' +import Popover from 'primevue/popover' +import { SearchQueryParams, type OcSearchQuery } from '@/declarations' +import { useI18n } from 'vue-i18n' +import { ref } from 'vue' +import OcMultipleField from '@/components/FormInputs/OcMultipleField/OcMultipleField.vue' +import { searchMetadata } from '@/modelMetadata/search' +import { useTranslateValue } from '@/composables/useTranslateValue' + +const { t } = useI18n() +const { translateValue } = useTranslateValue() + +const model = defineModel<OcSearchQuery>({ + required: true, + default: { + params: {} + } +}) + +const props = defineProps({ + loading: { + type: Boolean, + default: false + } +}) + +const emit = defineEmits<{ + submit: [] +}>() + +const searchPop = ref() + +function handleChange(newValue: any, key: string) { + model.value.params[key as keyof typeof SearchQueryParams] = newValue +} +</script> +<style scoped> +:deep(#popover) { + position: relative !important; + left: 0 !important; + top: 0 !important; + z-index: 1 !important; + --p-popover-arrow-offset: 0.3rem +} +</style> diff --git a/src/declarations.ts b/src/declarations.ts index 10a0d66145764c80bddff0e5a8e71a7f6ad7506f..93cd00b93bce25379473ee58f3d56a19b2b81b53 100644 --- a/src/declarations.ts +++ b/src/declarations.ts @@ -147,9 +147,11 @@ export type OcSearchResult = OcResource & { /** A representation of a search query */ export type OcSearchQuery = { q?: string - params?: Partial<Record<keyof typeof SearchQueryParams, string|string[]>> + params: OcSearchParameters } +export type OcSearchParameters = Partial<Record<keyof typeof SearchQueryParams, string[]>> + export enum SearchQueryParams { title = "http://purl.org/dc/terms/title", description = "http://purl.org/dc/terms/description", diff --git a/src/locales/en.ts b/src/locales/en.ts index e3c9657c5814963d4008f12061b97976ff16da5c..27f1f3afdc90af48bdb82c324991e56912c9462c 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -348,6 +348,7 @@ Greetings, title: "Search", resourceType: "Resource type", results: "Results", + launchSearch: "Launch the search", searchResult: { description: "Description", datasets: "Datasets", diff --git a/src/locales/fr.ts b/src/locales/fr.ts index 180ebb73898779e69e5a11fe8223e6568a0ee739..78684831f3edfcf040f46bb4a0032321902d60f1 100644 --- a/src/locales/fr.ts +++ b/src/locales/fr.ts @@ -363,6 +363,7 @@ Cordialement, title: "Rechercher", resourceType: "Type de ressource", results: "Résultats", + launchSearch: "Lancer la recherche", searchResult: { description: "Description", catalogues: "Sous-catalogues", diff --git a/src/modelMetadata/search.ts b/src/modelMetadata/search.ts new file mode 100644 index 0000000000000000000000000000000000000000..fe7799a195962731d78256b2018358f13980e9ba --- /dev/null +++ b/src/modelMetadata/search.ts @@ -0,0 +1,22 @@ +import type { OcModelMetadata, OcSearchParameters } from "@/declarations"; + +export const searchMetadata: OcModelMetadata<OcSearchParameters> = { + title: { + label: { + fr: "Titre", + en: "Title" + } + }, + description: { + label: { + fr: "Description", + en: "Description" + } + }, + creator: { + label: { + fr: "Auteur", + en: "Author" + } + } +} diff --git a/src/pages/community/[community].search.vue b/src/pages/community/[community].search.vue index c537edc6da08590cb21aa2ed8cf5d58e798827de..8423e6fc87be5e28172e973eb9b316adfb00f258 100644 --- a/src/pages/community/[community].search.vue +++ b/src/pages/community/[community].search.vue @@ -5,18 +5,11 @@ <h1 class="font-title text-4xl uppercase font-bold mb-4 text-primary"> {{ t('search.title') }} </h1> - <IconField class="w-full mt-8 mb-4"> - <InputText - id="search" - v-model="searchQuery.q" - fluid - size="large" - :placeholder="t('community.homepage.searchBarPlaceholder')" - v-on:keyup.enter="submit" - :disabled="searching" - /> - <InputIcon class="fa-solid fa-magnifying-glass" :onclick="submit" /> - </IconField> + <OcResourceSearchSimple + v-model="searchQuery" + :loading="searching" + v-on:submit="submit" + /> <div v-if="!searching"> <h4 v-if="searched" class="font-bold text-xl mb-4"> {{ searchResultList.length }} {{ t('search.results') }} @@ -59,14 +52,12 @@ import OcLayoutSimple from '@/layout/OcLayoutSimple/OcLayoutSimple.vue' import { useAccountStore } from '@/stores/account' import { computed, onBeforeMount, ref, watch } from 'vue' import { useI18n } from 'vue-i18n' -import IconField from 'primevue/iconfield' -import InputText from 'primevue/inputtext' -import InputIcon from 'primevue/inputicon' import OcSearchResultCard from '@/components/Search/OcSearchResultCard/OcSearchResultCard.vue' import OcSearchResultCardSkeleton from '@/components/Search/OcSearchResultCardSkeleton/OcSearchResultCardSkeleton.vue' import { getSearchResults, searchResources } from '@/sparql/search' import Paginator from 'primevue/paginator' import { useRoute, useRouter } from 'vue-router' +import OcResourceSearchSimple from '@/components/Search/OcResourceSearchSimple/OcResourceSearchSimple.vue' definePage({ name: 'community.search', @@ -89,7 +80,7 @@ const router = useRouter() const itemsPerPage = 5 const start = ref(0) -const searchQuery = ref<OcSearchQuery>({}) +const searchQuery = ref<OcSearchQuery>({params: {}}) const searchResultList = ref<string[]>([]) const searchResultObjects = ref<OcSearchResult[]>([]) @@ -107,6 +98,7 @@ const submit = () => { }, query: { q: searchQuery.value.q, + ...searchQuery.value.params, start: start.value } }) @@ -166,21 +158,22 @@ const breadcrumbItems = computed<OcBreadcrumbItem[]>(() => [ ]) const loadRouteParams = () => { - searchQuery.value.q = route.query.q as string ?? "" + if (route.query.q){ searchQuery.value.q = route.query.q as string } searchQuery.value.params = {} for (const param in SearchQueryParams){ if (Array.isArray(route.query[param])){ - searchQuery.value.params[param] = route.query[param] as string[] + searchQuery.value.params[param as keyof typeof SearchQueryParams] = route.query[param] as string[] } else { - searchQuery.value.params[param] = route.query[param] as string + if (route.query[param]){ + searchQuery.value.params[param as keyof typeof SearchQueryParams] = [route.query[param] as string] + } } } start.value = route.query.start ? parseInt(route.query.start as string) : 0 - console.log(searchQuery.value) } -watch(() => route.query.q, search) watch(() => route.query.page, () => { getResultsForStart(start.value)}) +watch(() => route.query, search) onBeforeMount(() => { if (Object.keys(route.query).length > 0) { diff --git a/src/pages/community/[community]/index.vue b/src/pages/community/[community]/index.vue index 7d538eac394c239ad5fc89f91abe6eb56a9204fe..4d1d5965dae8dfee224403be2d339f2b50c160e3 100644 --- a/src/pages/community/[community]/index.vue +++ b/src/pages/community/[community]/index.vue @@ -31,7 +31,7 @@ :placeholder="t('community.homepage.searchBarPlaceholder')" v-on:keyup.enter="search" /> - <InputIcon class="fa-solid fa-magnifying-glass" @click="search"/> + <InputIcon class="fa-solid fa-magnifying-glass cursor-pointer hover:text-primary" @click="search"/> </IconField> </section> diff --git a/src/sparql/search.ts b/src/sparql/search.ts index 1a9e8df43babd043588f840d12b3e9275860ace9..fea4c5bafb4946e2bbf67f54a0503464be325b2b 100644 --- a/src/sparql/search.ts +++ b/src/sparql/search.ts @@ -7,58 +7,39 @@ import { resourceContext } from './resource' * Get results URI of a search query */ export const searchResources = async (query: OcSearchQuery, auth?: Credentials) => { + console.log(query) let queryFilter = '' if (query.q) { queryFilter = `FILTER regex(?o, "${query.q}", "i")` } if (query.params?.title){ - queryFilter += '?resource dct:title ?title.\n' - if (Array.isArray(query.params.title)){ - query.params.title.forEach((title, index) => { - queryFilter += ` - ?resource dct:title ?title${index}. - FILTER regex(?title${index}, "${title}", "i") + query.params.title.forEach((title, index) => { + queryFilter += ` + ?resource dct:title ?title${index}. + FILTER regex(?title${index}, "${title}", "i") ` - }) - } else { - queryFilter += `FILTER regex(?title, "${query.params.title}", "i")\n` - } - + }) } if (query.params?.description){ - queryFilter += '?resource dct:description ?description.\n' - if (Array.isArray(query.params.description)){ - query.params.description.forEach((description, index) => { - queryFilter += ` - ?resource dct:description ?description${index}. - FILTER regex(?description${index}, "${description}", "i") - ` - }) - } else { - queryFilter += `FILTER regex(?description, "${query.params.description}", "i")\n` - } + query.params.description.forEach((description, index) => { + queryFilter += ` + ?resource dct:description ?description${index}. + FILTER regex(?description${index}, "${description}", "i") + ` + }) } if (query.params?.creator){ - queryFilter += ` - ?resource dct:creator ?creator. - VALUES ?pCreator { foaf:name foaf:givenName foaf:familyName foaf:firstName } - ?creator ?pCreator ?oCreator. - ` - if (Array.isArray(query.params.creator)){ - query.params.creator.forEach((creator, index) => { - queryFilter += ` - ?resource dct:creator ?creator${index}. - VALUES ?pCreator${index} { foaf:name foaf:givenName foaf:familyName foaf:firstName } - ?creator${index} ?pCreator${index} ?oCreator${index}. - FILTER regex(?oCreator${index}, "${creator}", "i") - ` - }) - } else { - queryFilter += `FILTER regex(?oCreator, "${query.params.creator}", "i")\n` - } + query.params.creator.forEach((creator, index) => { + queryFilter += ` + ?resource dct:creator ?creator${index}. + VALUES ?pCreator${index} { foaf:name foaf:givenName foaf:familyName foaf:firstName } + ?creator${index} ?pCreator${index} ?oCreator${index}. + FILTER regex(?oCreator${index}, "${creator}", "i") + ` + }) } const res = await executeSparqlSelect( @@ -133,7 +114,7 @@ export const getSearchResults = async (resourceUriList: string[], auth?: Credent } } - const formattedUris = '<' + resourceUriList.join('> <') + '>' + const formattedUris = '<' + resourceUriList.map((uri) => uri.trim()).join('> <') + '>' const res = await executeSparqlConstruct<OcSearchResult>( `