From 8d33275ff66569175dca749b549dfee346e3f1a4 Mon Sep 17 00:00:00 2001 From: Remi PLANEL <rplanel@pasteur.fr> Date: Mon, 26 Feb 2024 13:10:37 +0100 Subject: [PATCH] get article from meilisearch + cache --- components/content/ArticleDoi.vue | 21 ++--- composables/useFetchArticle.ts | 129 ++++++++++-------------------- 2 files changed, 50 insertions(+), 100 deletions(-) diff --git a/components/content/ArticleDoi.vue b/components/content/ArticleDoi.vue index fa7e1b86..bf4ae550 100644 --- a/components/content/ArticleDoi.vue +++ b/components/content/ArticleDoi.vue @@ -1,6 +1,6 @@ <script setup lang="ts"> import { useDisplay } from "vuetify"; -import { useArticlesStore } from '@/stores/articles' +import type { WikiArticle } from '@/types/articles'; export interface Props { index?: number; @@ -19,11 +19,14 @@ const props = withDefaults(defineProps<Props>(), { isRelevant: false, }); -const { article } = useFetchArticle(props.doi); +const article = ref<WikiArticle | undefined>(undefined) const { mobile } = useDisplay(); const show = ref(false); - +onMounted(async () => { + const { article: articleOnMounted } = await useFetchArticle(props.doi); + article.value = articleOnMounted.value +}) const articleTitle = computed(() => { return props?.title ?? article?.value?.title ?? props.doi; }); @@ -32,16 +35,11 @@ const articleAbstract = computed(() => { }); </script> <template> - <v-list-item :href="article?.href" :id="`ref-${props.doi}`" :target="article?.target" density="compact" color="transparent" - class="px-1"> + <v-list-item :href="article?.href" :id="`ref-${props.doi}`" :target="article?.target" density="compact" + color="transparent" class="px-1"> <template v-if="!mobile" #prepend> <v-icon icon="md:star" :color="props.isRelevant ? 'info' : 'transparent'"></v-icon> </template> - <!-- <template v-if="!mobile" #append> - <v-btn v-if="articleAbstract" size="x-small" variant="plain" - :append-icon="show ? 'mdi-chevron-up' : 'mdi-chevron-down'" class="px-0" - @click.stop.prevent="show = !show">Abstract</v-btn> - </template> --> <v-card flat color="transparent" density="compact" class="my-0 article-ref"> <v-card-item density="compact" class="pa-0"> <v-toolbar class="py-0 d-flex align-start article-toolbar" color="transparent" :height="20"> @@ -51,9 +49,6 @@ const articleAbstract = computed(() => { :append-icon="show ? 'mdi-chevron-up' : 'mdi-chevron-down'" class="px-1 align-center" @click.stop.prevent="show = !show">Abstract</v-btn> </v-toolbar> - <!-- <v-card-title class="py-0"><span class="font-weight-bold">{{ - articleTitle - }}</span></v-card-title> --> <v-card-subtitle class="py-0"> {{ article?.subtitle ?? "no authors" }}</v-card-subtitle> <v-card-subtitle class="py-0"> diff --git a/composables/useFetchArticle.ts b/composables/useFetchArticle.ts index 760b25c9..eef82b70 100644 --- a/composables/useFetchArticle.ts +++ b/composables/useFetchArticle.ts @@ -1,26 +1,22 @@ -import { useArticlesStore } from '../stores/articles' -import { ref, computed, watchEffect, toValue } from "vue" -// import { useFetch } from '#app'; -// import { useFetch } from "nuxt" -import { type MaybeRef, useFetch } from '#imports' -import type { CslJson, CrossrefArticle, SrcArticle } from '@/types/articles'; +import { ref, toValue } from "vue" +import { StorageSerializers } from "@vueuse/core" +import type { CslJson, WikiArticle } from '@/types/articles'; +import type { SearchParams } from 'meilisearch' - - - - - -export function useFetchArticle(doi: MaybeRef<string> = ref("")) { - // const article = ref<Article>() - // const rawArticle = ref<RawArticle>() - const srcArticle = ref<SrcArticle | null>(null) - const store = useArticlesStore() - const pending = ref(false) +export async function useFetchArticle(doi: string = "") { const doiBaseUrl = ref(new URL("https://doi.org/")); - const url = ref(new URL(`/works/${toValue(doi)}`, " https://api.crossref.org/").href); - const article = ref() - const zoteroArticles = ref() - + // const article = ref<WikiArticle | undefined>(undefined) + const article = useSessionStorage<WikiArticle>(doi, null, { serializer: StorageSerializers.object }) + const client = useMeiliSearchRef() + const index = ref("article") + const params = ref<SearchParams>({ + facets: ["*"], + filter: [`DOI='${doi}'`], + limit: 1 + }) + function getReferenceUrl(doi: string) { + return new URL(doi, doiBaseUrl.value).href; + } function toAuthorsString(authors: Array<{ family: string; given: string }>) { return authors .map((curr) => { @@ -28,12 +24,7 @@ export function useFetchArticle(doi: MaybeRef<string> = ref("")) { }) .join(", "); } - - function getReferenceUrl(doi: string) { - return new URL(doi, doiBaseUrl.value).href; - } - - function zoteroArticleToArticle(zoteroArticle: CslJson) { + function zoteroArticleToArticle(zoteroArticle: CslJson): WikiArticle | undefined { if (zoteroArticle != undefined) { const { DOI, @@ -56,74 +47,38 @@ export function useFetchArticle(doi: MaybeRef<string> = ref("")) { target: "_blank", prependIcon: "mdi-newspaper-variant-outline", } - } + } else { return undefined } + } + if (!article.value) { + const { data, error } = await useAsyncData( + doi, + async () => { + return await client.index(toValue(index)).search<CslJson>("", toValue(params)) + }, + { + transform: function (data) { + if (data !== undefined && data?.hits.length >= 1) { + return zoteroArticleToArticle(data.hits[0]) + } + } + } + ) - } - function crossrefToArticle(article: CrossrefArticle): WikiArticle { - const { title, DOI, type, "container-title": ct, "short-container-title": sct, abstract, author, issued } = article - // let sanitizedAbstract = abstract - const sanitizedAbstract = abstract ? /(?:\<jats\:p\>)?(.*)(?:\<\/jats\:p\>)?/.exec(abstract)?.[1] ?? '' : '' - const sanitizedContainerTitle = sct?.length > 0 ? sct[0] : ct?.length > 0 ? ct[0] : "" - return { - title: title?.length > 0 ? title[0] : "", - DOI, - abstract: sanitizedAbstract, - containerTitle: sanitizedContainerTitle, - subtitle: toAuthorsString(author || []), - author, - year: issued?.["date-parts"][0][0] ?? '', - href: getReferenceUrl(DOI), - target: "_blank", - prependIcon: "mdi-newspaper-variant-outline" + if (error.value) { + throw createError({ + ...error.value, + statusMessage: `Could not fetch article ${doi}` + }) } + article.value = data.value } + return { article } +} - function crossrefToCsl(article: CrossrefArticle): CslJson { - const { title, DOI, type, "container-title": ct, "short-container-title": sct, abstract, author, issued } = article - // let sanitizedAbstract = abstract - const sanitizedAbstract = abstract ? /(?:\<jats\:p\>)?(.*)(?:\<\/jats\:p\>)?/.exec(abstract)?.[1] ?? '' : '' - const sanitizedContainerTitle = sct?.length > 0 ? sct[0] : ct?.length > 0 ? ct[0] : "" - return { - title: title?.length > 0 ? title[0] : "", - type, - DOI, - abstract: sanitizedAbstract, - author, - "container-title": sanitizedContainerTitle, - issued - } - } - if (store.articles.has(toValue(doi))) { - const cslArticle = store.articles.get(toValue(doi)) - article.value = cslArticle ? zoteroArticleToArticle(cslArticle) : undefined - } - else { - useFetch<RawArticle>(toValue(url), { - lazy: true, server: false, - }).then(({ data, pending: pendingUseFetch }) => { - if (data.value?.message) { - article.value = crossrefToArticle(data.value.message) - store.add(crossrefToCsl(data.value.message)) - } - pending.value = pendingUseFetch.value - }) - } - // const fetchCrossRef = () => { - // useFetch<RawArticle>(toValue(url), { - // lazy: true, server: false, - // }).then(({ data, pending: pendingUseFetch }) => { - // if (data.value?.message) { - // srcArticle.value = data.value.message - // } - // pending.value = pendingUseFetch.value - // }) - // } - return { article, pending } -} -- GitLab