diff --git a/src/components/OcTopMenu.vue b/src/components/OcTopMenu.vue index ffc954455a853dbd6cfff16be3e445b09e273ea9..ac4e5c4d1540989aa04f08235ad8910e6b0e5a0d 100644 --- a/src/components/OcTopMenu.vue +++ b/src/components/OcTopMenu.vue @@ -49,7 +49,8 @@ const menuItems = computed(() => { params: { ...route.params, lang: key - } + }, + query: route.query }) } }) diff --git a/src/pages/community/[community].search.vue b/src/pages/community/[community].search.vue index 4b383704ecfe447e4e844547cde8530e8363dd90..7fa863df82776976f960987e4b490cf38825c16d 100644 --- a/src/pages/community/[community].search.vue +++ b/src/pages/community/[community].search.vue @@ -5,16 +5,30 @@ > <div class="flex flex-row"> <div class="p-4 pl-8 w-2/12 ml-auto mr-auto"> - <Panel :header="t('search.resourceType')" toggleable> - <template #toggleicon="toggleIconProps"> - <i v-if="toggleIconProps.collapsed" class="fa-solid fa-chevron-down text-gray-700"/> - <i v-else class="fa-solid fa-chevron-up text-gray-700"/> - </template> - <div class="flex items-center gap-2" v-for="(dcatType, uri) in type2ResourceType" v-bind:key="uri"> - <Checkbox v-model="resourceType" :inputId="uri" name="resourceType" :value="uri" /> - <label for="resourceType" class="capitalize"> {{ t('resourceType.'+dcatType) }} </label> - </div> - </Panel> + <form id="searchForm" method="get" ref="searchForm" v-on:submit="submit"> + <Panel :header="t('search.resourceType')" toggleable> + <template #toggleicon="toggleIconProps"> + <i v-if="toggleIconProps.collapsed" class="fa-solid fa-chevron-down text-gray-700" /> + <i v-else class="fa-solid fa-chevron-up text-gray-700" /> + </template> + <div + class="flex items-center gap-2" + v-for="(dcatType, uri) in type2ResourceType" + v-bind:key="uri" + > + <Checkbox + v-model="resourceType" + :inputId="uri" + name="resourceType" + :value="uri" + @change="submit" + /> + <label for="resourceType" class="capitalize"> + {{ t('resourceType.' + dcatType) }} + </label> + </div> + </Panel> + </form> </div> <div class="p-4 pl-8 w-9/12 ml-auto mr-auto"> <h1 class="font-title text-4xl uppercase font-bold mb-4 text-primary"> @@ -23,12 +37,13 @@ <IconField class="w-full mt-8 mb-4"> <InputText id="search" + form="searchForm" v-model="queryString" fluid size="large" :placeholder="t('community.homepage.searchBarPlaceholder')" /> - <InputIcon class="fa-solid fa-magnifying-glass" :onclick="search" /> + <InputIcon class="fa-solid fa-magnifying-glass" :onclick="submit" /> </IconField> <div v-if="!searching"> <template v-for="result in searchResultObjects" v-bind:key="result"> @@ -44,11 +59,11 @@ </div> <Paginator class="mt-8" - always-show="false" + :always-show="false" :rows="itemsPerPage" + v-model:first="start" :total-records="searchResultList.length" - v-bind:currentPage="currentPage" - v-on:page="getResultsForPage($event.page)" + v-on:page="submit" /> </div> </div> @@ -62,7 +77,7 @@ import { useCommunityData } from '@/dataLoaders/community' import type { OcBreadcrumbItem, OcSearchResult } from '@/declarations' import OcLayoutSimple from '@/layout/OcLayoutSimple/OcLayoutSimple.vue' import { useAccountStore } from '@/stores/account' -import { computed, ref } from 'vue' +import { computed, onBeforeMount, ref, watch } from 'vue' import { useI18n } from 'vue-i18n' import IconField from 'primevue/iconfield' import InputText from 'primevue/inputtext' @@ -74,6 +89,7 @@ import Paginator from 'primevue/paginator' import Panel from 'primevue/panel' import Checkbox from 'primevue/checkbox' import { type2ResourceType } from '@/helpers/resourceType' +import { useRoute, useRouter } from 'vue-router' definePage({ name: 'community.search', @@ -84,17 +100,20 @@ definePage({ }) const accountStore = useAccountStore() -const { t } = useI18n() +const { locale, t } = useI18n() const { translateValue } = useTranslateValue() const { data: community } = useCommunityData() +const route = useRoute() +const router = useRouter() + const itemsPerPage = 5 -const currentPage = ref(0) +const start = ref(route.query.start ?? 0) -const queryString = ref('') -const resourceType = ref([]) +const queryString = ref(route.query.q) +const resourceType = ref(route.query.type) const searchResultList = ref<string[]>([]) const searchResultObjects = ref<OcSearchResult[]>([]) @@ -102,13 +121,28 @@ const searchResultObjects = ref<OcSearchResult[]>([]) const searching = ref(false) const errorMessage = ref<string | null>(null) +const submit = () => { + router.push({ + name: "community.search", + params: { + lang: locale.value, + community: community.value.name + }, + query: { + q: queryString.value, + type: resourceType.value, + start: start.value + } + }) +} + const search = async () => { if (searching.value) { return } searching.value = true - currentPage.value = 0 + start.value = 0 try { searchResultList.value = await searchResources( { @@ -122,31 +156,28 @@ const search = async () => { errorMessage.value = (e as Error).message ?? 'Error' } - try { - searchResultObjects.value = await getSearchResults( - searchResultList.value.slice(0, itemsPerPage), - accountStore.auth - ) - } catch (e) { - console.error(e) - errorMessage.value = (e as Error).message ?? 'Error' - } + getResultsForStart(start.value) searching.value = false } -const getResultsForPage = async (pagenumber: number) => { - searching.value = true - try { - searchResultObjects.value = await getSearchResults( - searchResultList.value.slice(pagenumber * itemsPerPage, (pagenumber + 1) * itemsPerPage), - accountStore.auth - ) - } catch (e) { - console.error(e) - errorMessage.value = (e as Error).message ?? 'Error' +const getResultsForStart = async (start: number) => { + const resourceUriList = searchResultList.value.slice(start, start + itemsPerPage) + if (resourceUriList.length){ + searching.value = true + try { + searchResultObjects.value = await getSearchResults( + resourceUriList, + accountStore.auth + ) + } catch (e) { + console.error(e) + errorMessage.value = (e as Error).message ?? 'Error' + } + searching.value = false + } else { + console.log("No results") } - searching.value = false } const breadcrumbItems = computed<OcBreadcrumbItem[]>(() => [ @@ -162,13 +193,37 @@ const breadcrumbItems = computed<OcBreadcrumbItem[]>(() => [ type: 'search' } ]) + +watch( + () => route.query, + (newQuery, oldQuery) => { + if (newQuery.q != oldQuery.q || newQuery.type != oldQuery.type){ + start.value = parseInt(newQuery.start) ?? 0 + getResultsForStart(start.value) + } + else { + start.value = 0 + search() + } + } +) + +onBeforeMount(() => { + if (route.query.q || route.query.type){ + queryString.value = route.query.q as string ?? "" + resourceType.value = route.query.type as string[] ?? [] + start.value = parseInt(route.query.start as string) ?? 0 + search() + } +}) </script> + <style scoped> :deep(.p-panel-header) { - @apply bg-primary rounded-t text-white mb-2 + @apply bg-primary rounded-t text-white mb-2; } -:deep(.p-panel){ - @apply bg-gray-100 +:deep(.p-panel) { + @apply bg-gray-100; } -</style> \ No newline at end of file +</style> diff --git a/src/pages/community/[community]/index.vue b/src/pages/community/[community]/index.vue index 7177e849b3dc9a933e75e68a301447fe70d82f54..fb976153fa52bd003edc562b3a64ccc5332b8b9a 100644 --- a/src/pages/community/[community]/index.vue +++ b/src/pages/community/[community]/index.vue @@ -22,15 +22,19 @@ {{ t('community.homepage.searchBarLegend') }} </h3> + <form v-on:submit="search"> <IconField class="w-full mt-4 mb-4"> - <InputText - id="search" - fluid - size="large" - :placeholder="t('community.homepage.searchBarPlaceholder')" - /> - <InputIcon class="fa-solid fa-magnifying-glass" /> - </IconField> + <InputText + id="search" + v-model="queryString" + fluid + size="large" + :placeholder="t('community.homepage.searchBarPlaceholder')" + /> + <InputIcon class="fa-solid fa-magnifying-glass" @click="search"/> + </IconField> + </form> + </section> <section @@ -71,7 +75,7 @@ </template> <script setup lang="ts"> -import { computed } from 'vue' +import { computed, ref } from 'vue' import { useAccountStore } from '@/stores/account' import { useI18n } from 'vue-i18n' import { useTranslateValue } from '@/composables/useTranslateValue' @@ -85,6 +89,7 @@ import { getColor } from '@/helpers/communityColor' import { useCommunityData } from '@/dataLoaders/community' import { useTreeStore } from '@/stores/tree' import { useAbility } from '@casl/vue' +import { useRouter } from 'vue-router' definePage({ name: 'community', @@ -98,9 +103,10 @@ treeStore.state.selectedNodeKey = undefined const { data: community } = useCommunityData() -const { t } = useI18n() +const { locale, t } = useI18n() const { translateValue } = useTranslateValue() const { cannot } = useAbility() +const router = useRouter() const account = useAccountStore() @@ -108,4 +114,24 @@ const title = computed(() => translateValue(community.value?.title)) const abstract = computed(() => translateValue(community.value?.abstract)) const logoUrl = computed(() => community.value?.logo ?? null) const color = computed(() => getColor(community.value)) + +const isMemberOfCurrentCommunity = computed( + () => community.value?.['@id'] && memberCommunitiesList.value.includes(community.value?.['@id']) +) + +const queryString = ref('') + +const search = () => { + router.push({ + name: 'community.search', + params: { + lang: locale.value, + community: community.value.name, + }, + query: { + q: queryString.value, + start: 0 + } + }) +} </script> diff --git a/src/sparql/search.ts b/src/sparql/search.ts index 04e66d9dd7beb84cc352f6d4feb7cfea3f3f1959..5918bc9254939a5e8f7e603a53d8b734f22c47e0 100644 --- a/src/sparql/search.ts +++ b/src/sparql/search.ts @@ -7,34 +7,36 @@ import { resourceContext } from './resource' * Get results URI of a search query */ export const searchResources = async (query: OcSearchQuery, auth?: Credentials) => { - let typeFilter = "" - if (query.resourceType?.length){ - typeFilter = "FILTER (?type = <" + let typeFilter = '' + if (query.resourceType?.length) { + typeFilter = 'FILTER (?type = <' typeFilter += query.resourceType.join('> || ?type = <') - typeFilter += ">)" + typeFilter += '>)' } - let queryFilter = "" - if (query.queryString){ + let queryFilter = '' + if (query.queryString) { queryFilter = `FILTER regex(?o, "${query.queryString}", "i")` } - const res = await executeSparqlSelect( - ` + const res = await executeSparqlSelect( + ` SELECT DISTINCT ?resource WHERE { ?resource rdf:type ?type. + ?resource dct:identifier ?identifier. ${typeFilter} FILTER EXISTS { VALUES ?p { dct:title dct:description } ?resource ?p ?o ${queryFilter} } - } + } ORDER BY ASC(?identifier) `, - { - auth: auth, - }) + { + auth: auth + } + ) return res.map((result) => result.resource.value) } @@ -42,7 +44,25 @@ export const searchResources = async (query: OcSearchQuery, auth?: Credentials) /** Get searchResults objects from URI list */ export const getSearchResults = async (resourceUriList: string[], auth?: Credentials) => { const searchResultContext: ContextDefinition = { - ...resourceContext, + '@type': { + '@container': '@set' + }, + identifier: { + '@id': 'http://purl.org/dc/terms/identifier' + }, + title: { + '@id': 'http://purl.org/dc/terms/title', + '@container': '@language' + }, + description: { + '@id': 'http://purl.org/dc/terms/description', + '@container': '@language' + }, + graph: { + '@id': 'https://www.irit.fr/opencommon/terms/graph', + '@type': '@id', + '@container': ['@set'] + }, catalog: { '@id': 'http://www.w3.org/ns/dcat#catalog', '@type': '@id', @@ -57,17 +77,17 @@ export const getSearchResults = async (resourceUriList: string[], auth?: Credent '@id': 'http://www.w3.org/ns/dcat#distribution', '@type': '@id', '@container': '@set' - }, + } } const formattedUris = '<' + resourceUriList.join('> <') + '>' return await executeSparqlConstruct<OcSearchResult>( - ` + ` CONSTRUCT { ?s ?p ?o. ?s oct:graph ?g. - ?s dcat:dataset ?dataset + ?s dct:identifier ?identifier. } WHERE { VALUES ?s { ${formattedUris} } @@ -88,8 +108,9 @@ export const getSearchResults = async (resourceUriList: string[], auth?: Credent } } `, - { - context: searchResultContext, - auth: auth, - }) -} \ No newline at end of file + { + context: searchResultContext, + auth: auth + } + ) +} diff --git a/typed-router.d.ts b/typed-router.d.ts index bb4d634acc9ccb77c5f192111635049f9d9c8bec..341f99c91b3177ba85a913ec72f0fd89449e089a 100644 --- a/typed-router.d.ts +++ b/typed-router.d.ts @@ -30,7 +30,7 @@ declare module 'vue-router/auto-routes' { 'community.dashboard.dataset': RouteRecordInfo<'community.dashboard.dataset', '/:lang/community/:community/dashboard/dataset', { community: ParamValue<true> }, { community: ParamValue<false> }>, 'community.datasets.new': RouteRecordInfo<'community.datasets.new', '/:lang/community/:community/dataset/new', { community: ParamValue<true> }, { community: ParamValue<false> }>, 'community.join': RouteRecordInfo<'community.join', '/:lang/community/:community/join', { community: ParamValue<true> }, { community: ParamValue<false> }>, - '/:lang/community/[community].search': RouteRecordInfo<'/:lang/community/[community].search', '/:lang/community/:community/search', { community: ParamValue<true> }, { community: ParamValue<false> }>, + 'community.search': RouteRecordInfo<'community.search', '/:lang/community/:community/search', { community: ParamValue<true> }, { community: ParamValue<false> }>, 'connection': RouteRecordInfo<'connection', '/:lang/connection', Record<never, never>, Record<never, never>>, 'logout': RouteRecordInfo<'logout', '/:lang/logout', Record<never, never>, Record<never, never>>, 'profile': RouteRecordInfo<'profile', '/:lang/profile', Record<never, never>, Record<never, never>>,