Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • mdm-lab/wiki
  • hvaysset/wiki
  • jsousa/wiki
  • tclabby/wiki
4 results
Show changes
Commits on Source (4)
Showing
with 1371 additions and 172 deletions
......@@ -102,14 +102,14 @@ build:df-wiki-cli:
--values deploy/meilisearch/values.yaml
--values deploy/meilisearch/values.${ENV:-development}.yaml
# wait for it to start
- MEILI_POD=$(kubectl -n=${KUBE_NAMESPACE} get po -l app.kubernetes.io\/instance=${CI_PROJECT_NAME}-${CI_ENVIRONMENT_NAME}-meilisearch,app.kubernetes.io\/name=meilisearch --output jsonpath='{.items[0].metadata.name}')
- |
until kubectl -n=${KUBE_NAMESPACE} wait --for=condition=ready pod ${MEILI_POD} --timeout=1s
do
date
sleep 1
kubectl -n=${KUBE_NAMESPACE} get po
done
# - MEILI_POD=$(kubectl -n=${KUBE_NAMESPACE} get po -l app.kubernetes.io\/instance=${CI_PROJECT_NAME}-${CI_ENVIRONMENT_NAME}-meilisearch,app.kubernetes.io\/name=meilisearch --output jsonpath='{.items[0].metadata.name}')
# - |
# until kubectl -n=${KUBE_NAMESPACE} wait --for=condition=ready pod ${MEILI_POD} --timeout=1s
# do
# date
# sleep 1
# kubectl -n=${KUBE_NAMESPACE} get po
# done
deploy:meilisearch:dev:
......@@ -184,7 +184,7 @@ lint:
stage: lint
script:
- cd content/3.defense-systems
- find . -name '*.md' ! -name '0.index.md' -print0 | xargs -0 -I {} df-wiki-cli content lint --file {}
- find . -name '*.md' ! -name '0.index.md' | sort | xargs -I {} df-wiki-cli content lint --file {}
when: manual
# Update Meili search indexes
......@@ -272,10 +272,10 @@ get-zotero:
extends: .df-wiki-cli-run
stage: get-data
script:
- df-wiki-cli articles --key ${ZOTERO_API_KEY} --output public/articles.json
- df-wiki-cli articles --key ${ZOTERO_API_KEY} --output content/_data/_articles.json
artifacts:
paths:
- public/articles.json
- content/_data/_articles.json
rules:
- if: $CI_COMMIT_BRANCH == "main"
......@@ -322,6 +322,9 @@ build:dev:wiki:
# - get-pfam
variables:
BASE_URL: /wiki/
before_script:
- *docker-login
# - "sed -i 's/MEILISEARCH_API_KEY/${$MEILI_API_KEY}/g' nuxt.config.ts"
rules:
- if: $CI_COMMIT_BRANCH != "main"
......
......@@ -17,6 +17,10 @@ ARG MEILI_HOST=http://localhost:7700
ARG MEILI_API_KEY=api_key
ENV NUXT_APP_BASE_URL=${BASE_URL}
# nuxt module
ENV NUXT_PUBLIC_MEILISEARCH_CLIENT_HOST_URL=${MEILI_HOST}
ENV NUXT_PUBLIC_MEILISEARCH_CLIENT_SEARCH_API_KEY=${MEILI_API_KEY}
ENV NUXT_PUBLIC_MEILI_HOST=${MEILI_HOST}
ENV NUXT_PUBLIC_MEILI_API_KEY=${MEILI_API_KEY}
......@@ -24,6 +28,7 @@ ENV NUXT_PUBLIC_MEILI_API_KEY=${MEILI_API_KEY}
WORKDIR /usr/src/app
COPY --from=install /usr/src/app ./
COPY . /usr/src/app
EXPOSE 3000 24678 4000
CMD ["npm", "run", "dev"]
......@@ -51,8 +56,13 @@ ARG BASE_URL=/
ARG MEILI_HOST=http://localhost:7700
ARG MEILI_API_KEY
ENV NODE_OPTIONS=--max_old_space_size=8192
ENV NODE_OPTIONS=--max_old_space_size=12288
ENV NUXT_APP_BASE_URL=${BASE_URL}
# nuxt module
ENV NUXT_PUBLIC_MEILISEARCH_CLIENT_HOST_URL=${MEILI_HOST}
ENV NUXT_PUBLIC_MEILISEARCH_CLIENT_SEARCH_API_KEY=${MEILI_API_KEY}
ENV NUXT_PUBLIC_MEILI_HOST=${MEILI_HOST}
ENV NUXT_PUBLIC_MEILI_API_KEY=${MEILI_API_KEY}
......@@ -60,6 +70,7 @@ ENV NUXT_PUBLIC_MEILI_API_KEY=${MEILI_API_KEY}
WORKDIR /usr/src/app
COPY --from=install /usr/src/app ./
COPY . /usr/src/app
RUN npm run generate
### STAGE: NGINX ###
......
<script setup lang="ts">
interface item {
title: string;
href?: string | undefined
}
export interface Props {
accessions: string[];
items: item[];
itemsToDisplay?: number;
baseUrl: string;
}
const props = withDefaults(defineProps<Props>(), {
pfamString: null,
itemsToDisplay: 2,
items: () => [],
itemsToDisplay: 1,
});
// const accessions = computed(() => {
// if (props.accessionString === null) {
// return [];
// } else {
// return props.accessionString.split(",").map((acc) => acc.trim());
// }
// });
const show = ref(false);
function constructUrl(accession: string) {
return new URL(accession, props.baseUrl).toString();
}
</script>
<template>
<!-- class="d-inline-flex justify-start align-center" -->
<span v-if="show" class="d-flex flex-wrap align-center justify-start">
<template v-if="accessions.length > itemsToDisplay">
<template v-for="(acc) in accessions" :key="acc">
<v-chip :href="constructUrl(acc)" target="_blank" color="info" class="mr-1 my-1 align-self-center"
size="small">
{{ acc }}
<template v-if="items.length > itemsToDisplay">
<template v-for="item in items" :key="item.title">
<v-chip :href="item?.href" :target="item?.href === undefined ? item?.href : '_blank'" color="info"
class="mr-1 my-1 align-self-center" size="small">
{{ item.title }}
</v-chip>
</template>
</template>
<v-btn v-if="itemsToDisplay < accessions.length" variant="text" :icon="'mdi-chevron-up'"
@click="show = !show"></v-btn>
<v-btn v-if="itemsToDisplay < items.length" variant="text" :icon="'mdi-chevron-up'" @click="show = !show"></v-btn>
</span>
<span v-else class="d-flex flex-wrap align-center justify-start">
<template v-for="(acc, index) in accessions" :key="acc">
<v-chip v-if="index < itemsToDisplay || itemsToDisplay < 0" :href="constructUrl(acc)" target="_blank"
color="info" class="mr-1 my-1 align-self-center" size="small">
{{ acc }}
<template v-for="(item, index) in items" :key="item.title">
<v-chip v-if="index < itemsToDisplay || itemsToDisplay < 0" :href="item?.href"
:target="item?.href === undefined ? item?.href : '_blank'" color="info" class="mr-1 my-1 align-self-center"
size="small">
{{ item.title }}
</v-chip>
<template v-if="index === itemsToDisplay">
<v-chip v-if="!show" variant="text" class="text-grey text-caption align-self-center px-1"
@click="show = !show">
(+{{ accessions.length - itemsToDisplay }} others)
(+{{ items.length - itemsToDisplay }} others)
</v-chip>
<v-btn v-if="itemsToDisplay < accessions.length && !show" variant="text" :icon="'mdi-chevron-down'"
<v-btn v-if="itemsToDisplay < items.length && !show" variant="text" :icon="'mdi-chevron-down'"
@click="show = !show"></v-btn>
</template>
</template>
......
......@@ -3,12 +3,14 @@ export interface Props {
fluid?: boolean
toc?: boolean
edit?: boolean
navDrawer?: boolean
}
const props = withDefaults(defineProps<Props>(), {
fluid: false,
toc: true,
edit: true
edit: true,
navDrawer: true
});
const drawer = ref(true);
......@@ -28,16 +30,23 @@ function onScroll() {
<VApp>
<v-main style="min-height: 300px">
<v-container v-scroll="onScroll" :fluid="fluid">
<slot />
<!-- </v-card-text>
<v-row justify="center">
<v-col cols="auto">
<v-card flat color="transparent" :max-width="fluid ? undefined : 1280">
<slot />
<!-- </v-card-text>
</v-card> -->
<EditGitlab v-if="edit" />
<NavPrevNext v-if="edit" />
<EditGitlab v-if="edit" />
<NavPrevNext v-if="edit" />
</v-card>
</v-col>
</v-row>
</v-container>
<!-- <Footer></Footer> -->
</v-main>
<NavNavbar v-model:drawer="drawer" :density="density" />
<slot name="drawer" :drawer="drawer">
<NavNavbar v-model:drawer="drawer" :density="density" :drawer-enabled="navDrawer"/>
<slot v-if="navDrawer" name="drawer" :drawer="drawer">
<NavDrawer :drawer="drawer" />
</slot>
<NavTableOfContent v-if="toc" :links="page.body.toc.links" />
......
......@@ -13,18 +13,19 @@ import { useDisplay, useTheme } from "vuetify";
const { navigation } = useContent();
// const drawer = ref(true);
// const computedNavigation = computed(() => {
// return navigation.value
// .filter(({ _path }) => {
// return _path !== "/refseq";
// })
const computedNavigation = computed(() => {
console.log(navigation.value)
return navigation.value
.filter(({ layout }) => {
return layout !== "db"
})
// });
});
</script>
<template>
<v-navigation-drawer :model-value="drawer" :border="1" color="background">
<v-list nav density="compact" :lines="false">
<NavNavigation :navigation="navigation" />
<NavNavigation :navigation="computedNavigation" />
</v-list>
</v-navigation-drawer>
</template>
\ No newline at end of file
......@@ -5,6 +5,7 @@ import { useDisplay, useTheme } from "vuetify";
export interface Props {
density: 'prominent' | 'compact'
drawer: boolean
drawerEnabled: boolean
}
const runtimeConfig = useRuntimeConfig();
......@@ -15,7 +16,8 @@ const theme = useTheme();
const switchTheme = ref(false)
const props = withDefaults(defineProps<Props>(), {
density: "prominent",
drawer: true
drawer: true,
drawerEnabled: true
});
const emit = defineEmits(['update:drawer'])
function toggleTheme() {
......@@ -52,7 +54,7 @@ function toggleDrawer() {
</script>
<template>
<v-app-bar :elevation="0" border name="app-bar" :density="density" color="background">
<template #prepend>
<template v-if="drawerEnabled" #prepend>
<v-app-bar-nav-icon @click.stop="toggleDrawer"></v-app-bar-nav-icon>
<!-- <Logo height="45px" /> -->
</template>
......
<script setup lang="ts">
// import type { FacetDistribution } from "meilisearch";
import { useDisplay } from "vuetify";
import { useFacetsStore, type Facets } from '~~/stores/facets'
import { useMeiliSearch } from "#imports"
interface SortItem {
key: string,
order: boolean | 'asc' | 'desc'
}
export interface Props {
title?: string
db?: string
sortBy?: SortItem[]
facets: string[]
headers: { title: string, key: string }[]
itemValue: string
}
export interface FilterItem {
type: 'facet' | 'operator' | 'value' | 'text'
value: string
title: string
count?: number
deletable: boolean
props: {
[key: string]: any
// title: string
// value: any
}
// raw?: any
}
const props = withDefaults(defineProps<Props>(), {
title: '',
db: 'refseq',
sortBy: () => [{ key: "type", order: "asc" }],
});
const sortByRef = ref(toValue(props.sortBy))
const facetsRef = toRef(() => props.facets)
const { search: msSearch, result: msResult } = useMeiliSearch(props.db)
const facetStore = useFacetsStore()
const search: Ref<string> = ref("");
const filterOrSearch: Ref<FilterItem[] | null> = ref(null)
const hitsPerPage: Ref<number> = ref(25)
const limit = ref(1000)
const filterError: Ref<string | null> = ref(null)
const msFilter: Ref<string | undefined> = ref(undefined)
const page = ref(1)
let loading = ref(false)
const { height } = useDisplay();
const minTableHeight = ref(400)
const computedTableHeight = computed(() => {
const computedHeight = height.value - 350
return computedHeight > minTableHeight.value ? computedHeight : minTableHeight.value
})
const filterInputValues = computed(() => {
console.log("recompouted FILTER value")
if (filterOrSearch.value != null) {
return filterOrSearch.value.filter(({ props }) => props.type !== 'text')
} else {
return null
}
})
const queryInputValue = computed(() => {
console.log("recompouted TEXT value")
if (filterOrSearch.value !== null) {
const phrase = filterOrSearch.value
.filter((f) => {
return f.props.type === 'text'
})
.map((f) => {
return f.value
})
if (phrase.length > 1) {
return `${phrase.join(" ")}`
}
else { return phrase[0] }
} else {
return null
}
})
const isFilter = computed(() => {
return Array.isArray(filterOrSearch.value)
})
const msSortBy = computed(() => {
if (sortByRef.value.length > 0) {
return sortByRef.value.map((curr) => {
if (curr?.key && curr?.order) {
return `${curr.key}:${curr.order}`
}
else { return "" }
})
} else { return undefined }
})
const reactiveParams = reactive({
hitsPerPage: 25,
page: 1,
limit: 1000,
facets: ["*"],
filter: [],
sort: ["type:asc"],
// prefix_length: 3,
// attributesToHighlight: ["*"]
})
watch([reactiveParams, msSortBy, page], ([newParams, newSort, newPage]) => {
searchOrFilter()
})
onMounted(async () => {
searchOrFilter()
})
// Fetch results
const msError = computed(() => {
if (filterError.value?.type && filterError.value?.message) {
return filterError.value?.message
} else { return false }
})
async function searchOrFilter() {
try {
loading.value = true
// const q = queryInputValue.value === null ? "" : queryInputValue.value
const q = search.value
await msSearch(q, { ...reactiveParams, filter: msFilter.value, sort: msSortBy.value })
} catch (error: any) {
filterError.value = error
console.log(error)
}
finally {
loading.value = false
}
}
function clearFilterOrSearch() {
filterOrSearch.value = null
searchOrFilter()
}
watch(msFilter, async (fos) => {
console.log("the filter change")
console.log(msFilter)
console.log(fos)
searchOrFilter()
search.value = ''
})
watch(msResult, (newRes) => {
console.log(msResult)
console.log(newRes)
facetStore.setFacets({ facetDistribution: newRes.facetDistribution, facetStat: newRes.facetStat })
}, { deep: true })
watch(filterInputValues, (newSoF) => {
if (isFilter.value && filterInputValues.value !== null && filterInputValues.value?.length % 3 === 0) {
msFilter.value = filterInputValues.value.map((it, index) => {
const sanitizedValue = it.value.split("-")[0]
if (index >= 1 && (index + 1) % 3 === 1) {
return ` AND ${sanitizedValue}`
} else if ((index + 1) % 3 === 0) {
return `"${sanitizedValue}"`
} else {
return `${sanitizedValue}`
}
}).join("")
}
})
watch(search, () => { searchOrFilter() })
// watch(queryInputValue, (newQuery) => {
// searchOrFilter()
// })
const filterStep = computed(() => {
return filterInputValues.value !== null && filterInputValues.value.length > 0 ? filterInputValues.value?.length % 3 : null
})
const operatorItems = ref([
{
type: "operator", value: '=', title: "is", deletable: false, props: {
type: "operator", deletable: false
}
}, {
type: "operator", value: '!=', title: "is not", deletable: false, props: {
type: "operator",
deletable: false
}
},
])
const autocompleteItems = computed(() => {
const index = filterOrSearch.value?.length ?? 0
console.log(index)
if (filterStep.value === null || filterStep.value === 0) {
return props.facets.map(value => {
return {
type: "facet",
value: `${value}-${index}`,
title: value,
deletable: false,
props: {
deletable: false,
type: "facet"
}
}
})
}
if (filterStep.value === 1) {
return operatorItems.value.map(it => { return { ...it, value: `${it.value}-${index}`, } })
}
if (filterStep.value === 2) {
// get the facet value
if (Array.isArray(filterOrSearch.value)) {
const { type, value } = filterOrSearch.value?.slice(-2, -1)[0]
const sanitizedValue = value.split("-")[0]
console.log("compute new facets")
const facetDistri = facetStore.facets?.facetDistribution
console.log(facetDistri)
return facetDistri?.[sanitizedValue] ? Object.entries(facetDistri[sanitizedValue]).map(([key, val]) => {
return {
type: "value", value: `${key}-${index}`, title: key, count: val, deletable: true, props: {
type: "value", count: val, deletable: true
}
}
}) : []
}
}
})
const canAddTextSearch = computed(() => {
if (filterOrSearch.value !== null && filterOrSearch.value.length > 0) {
const lastItem = filterOrSearch.value.slice(-1)[0]
return lastItem?.props.type === 'value' || lastItem?.props.type === "text"
}
return true
})
function selectItem(item) {
filterOrSearch.value = Array.isArray(filterOrSearch.value) ? [...filterOrSearch.value, item] : [item]
}
function deleteOneFilter(index: number) {
console.log("deleteOnefilter")
console.log(index)
console.log(isFilter.value)
console.log(filterOrSearch)
if (isFilter.value) {
filterOrSearch.value?.splice(index - 2, 2)
console.log(filterOrSearch.value)
}
}
function deleteTextFilter(index: number) {
console.log("delete text filter")
console.log(index)
console.log(isFilter.value)
console.log(filterOrSearch)
console.log(filterOrSearch.value?.length)
if (isFilter.value) {
if (index === 0) {
filterOrSearch.value?.shift()
} else {
filterOrSearch.value?.splice(index, 1)
}
console.log(filterOrSearch.value?.length)
console.log(filterOrSearch.value)
}
}
function clearSearch() {
search.value = ""
}
function runTextSearch() {
if (canAddTextSearch) {
const item: FilterItem = reactive({
type: 'text', title: search.value, value: search.value, deletable: true, props: { type: "text", deletable: true, }
})
if (Array.isArray(filterOrSearch.value)) {
filterOrSearch.value = [
...filterOrSearch.value, item
]
} else {
filterOrSearch.value = [item]
}
search.value = ""
searchOrFilter()
}
}
function namesToCollapsibleChips(names: string[]) {
return names.filter((it) => it !== "").map(it => ({ title: it }))
}
function namesToAccessionChips(names: string[]) {
return namesToCollapsibleChips(names).map(it => {
return { ...it, href: new URL(it.title, "http://toto.pasteur.cloud").toString() }
})
}
</script>
<template>
<v-card flat>
<v-card-text>
<v-row>
<v-col cols="5">
<v-text-field v-model="search" label="Search..." hide-details prepend-inner-icon="mdi-magnify"
single-line clearable></v-text-field>
</v-col>
<v-col>
<v-autocomplete ref="autocompleteInput" hide-details v-model:model-value="filterOrSearch"
auto-select-first chips clearable label="Filter results..." :items="autocompleteItems" single-line
item-value="value" item-title="title" multiple return-object prepend-inner-icon="md:search"
@click:appendInner="searchOrFilter" @click:clear="clearFilterOrSearch"
@update:modelValue="() => clearSearch()">
<template #chip="{ props, item, index }">
<v-chip v-bind="props" :text="item.raw.title" :closable="item.props.deletable"
@click:close="item.props.type === 'text' ? deleteTextFilter(index) : deleteOneFilter(index)"></v-chip>
<!-- <v-chip v-if="(index + 1) % 3 === 0" v-bind="props" :text="item.raw.title" closable
@click:close="deleteOneFilter(index)"></v-chip>
<v-chip v-else v-bind="props" :text="item.raw.title"></v-chip> -->
</template>
<template #item="{ props, item }">
<v-list-item v-bind="{ ...props, active: false, onClick: () => selectItem(item) }"
:title="item.title" :subtitle="item.raw?.count ? item.raw.count : ''" :value="props.value">
</v-list-item>
</template>
<!-- <template #no-data></template>
<template #prepend-item>
<v-list-item v-if="canAddTextSearch" :title="`Text search: ${search}`" @click="runTextSearch"> </v-list-item>
</template> -->
</v-autocomplete>
</v-col>
</v-row>
</v-card-text>
<v-data-table-server v-if="!msError" v-model:page="reactiveParams.page"
v-model:items-per-page="reactiveParams.hitsPerPage" v-model:sortBy="sortByRef" fixed-header :loading="loading"
:headers="headers" :items="msResult?.hits ?? []" :items-length="msResult?.totalHits ?? 0"
:item-value="itemValue" multi-sort density="compact" :height="computedTableHeight" class="elevation-1 mt-2">
<template #[`item.accession_in_sys`]="{ item }">
<CollapsibleChips :items="namesToAccessionChips(item.accession_in_sys)"></CollapsibleChips>
</template>
<template #[`item.proteins_in_the_prediction`]="{ item }">
<CollapsibleChips :items="namesToCollapsibleChips(item.proteins_in_the_prediction)"></CollapsibleChips>
</template>
<template #[`item.system_genes`]="{ item }">
<CollapsibleChips :items="namesToCollapsibleChips(item.system_genes)"></CollapsibleChips>
</template>
<template #[`item.completed`]="{ item }">
<v-icon v-if="item.completed" color="success" icon="md:check"></v-icon>
<v-icon v-else color="warning" icon="md:dangerous"></v-icon>
</template>
</v-data-table-server>
<v-alert v-else type="error">
{{ msError }}
</v-alert>
</v-card>
</template>
\ No newline at end of file
<script setup lang="ts">
import { useDisplay } from "vuetify";
import { useArticlesStore } from '@/stores/articles'
export interface Props {
index?: number;
......@@ -14,10 +15,27 @@ const props = withDefaults(defineProps<Props>(), {
enumerate: true,
divider: false,
});
// onBeforeMount(async () => {
// await useArticlesStore().initialize()
// })
const { article } = useFetchArticle(props.doi);
const { mobile } = useDisplay();
const show = ref(false);
// const computedArticle = computed(() => { return { ...article.value } })
// watch(article, (newArticle) => {
// console.log("article updated")
// }, { deep: true })
console.log("aritcle dans composant")
console.log(article)
const articleTitle = computed(() => {
return props?.title ?? article?.value?.title ?? props.doi;
});
......@@ -26,8 +44,8 @@ const articleAbstract = computed(() => {
});
</script>
<template>
<v-list-item :href="article?.href" :id="props.doi" :target="article?.target" density="compact" color="transparent"
class="px-1">
<v-list-item :href="article?.href" :id="props.doi" :target="article?.target" density="compact"
color="transparent" class="px-1">
<template #prepend v-if="!mobile && enumerate">
<v-avatar color="primary" size="small" density="compact" variant="tonal">
{{ props?.index ?? "#" }}
......
<script setup lang="ts">
import { useFacetsStore } from '~~/stores/facets'
import * as Plot from "@observablehq/plot";
import PlotFigure from "~/components/PlotFigure";
import { useDisplay } from "vuetify";
const facetStore = useFacetsStore()
const sortBy: Ref<{ key: string, order: string }[]> = ref([{ key: 'type', order: "asc" }])
const itemValue = ref("id");
const { width } = useDisplay();
const distriTool: Ref<string[]> = ref([])
const facets = ref([
"type",
"Superkingdom",
"phylum",
"order",
"family",
"genus",
"species",
])
const availableTaxo: Ref<string[]> = ref([
"species",
"genus",
"family",
"order",
"phylum",
"Superkingdom"
]);
const selectedTaxoRank = ref("phylum");
const headers = ref([
{ title: "Replicon", key: "replicon" },
{
title: "Type",
key: "type",
},
{
title: "Subtype",
key: "subtype",
},
{
title: "Accessions",
key: "accession_in_sys"
}
])
const logTransform = computed(() => {
return distriTool.value.includes('log')
})
const fullWidth = computed(() => {
return distriTool.value.includes('fullwidth')
})
const computedHeaders = computed(() => {
return [...headers.value, ...availableTaxo.value.map(taxo => {
return {
title: capitalize(taxo),
key: taxo
}
})]
})
const computedWidth = computed(() => {
return Math.max(width.value, 550);
});
const plotHeight = computed(() => {
return computedWidth.value / 3;
// return 500
});
const defaultBarPlotOptions = computed(() => {
const y = logTransform.value ? { nice: true, grid: true, type: 'symlog' } : { nice: true, grid: true, type: "linear" }
// const y = { nice: true, grid: true }
return {
x: { label: null, tickRotate: 70 },
y,
color: { legend: true },
width: computedWidth.value,
height: plotHeight.value,
}
})
const computedSystemDistribution = computed(() => {
if (facetStore.facets?.facetDistribution?.type) {
return Object.entries(facetStore.facets.facetDistribution.type).map(([key, value]) => {
return {
type: key,
// count: logTransform.value ? Math.log(value) : value
count: value
}
}).sort()
} else { return [] }
})
const computedDistriSystemOptions = computed(() => {
return {
...defaultBarPlotOptions.value,
marginBottom: 120,
marks: [
// Plot.frame(),
Plot.barY(
toValue(computedSystemDistribution),
{
y: "count", x: 'type', tip: true,
fill: "#6750a4",
sort: { x: "-y" },
},
),
],
};
});
const computedTaxonomyDistribution = computed(() => {
if (facetStore.facets?.facetDistribution?.[selectedTaxoRank.value]) {
return Object.entries(facetStore.facets.facetDistribution[selectedTaxoRank.value]).map(([key, value]) => {
return {
[selectedTaxoRank.value]: key,
count: value
}
}).sort()
} else { return [] }
})
const computedDistriTaxoOptions = computed(() => {
return {
...defaultBarPlotOptions.value,
marginBottom: 200,
marks: [
Plot.barY(
toValue(computedTaxonomyDistribution),
{
y: "count",
x: selectedTaxoRank.value,
tip: true,
fill: "#6750a4",
sort: { x: "-y" },
}
),
],
};
});
function capitalize([first, ...rest]) {
return first.toUpperCase() + rest.join('').toLowerCase();
}
</script>
<template>
<v-card flat class="mb-2">
<v-toolbar density="compact">
<v-toolbar-title>Distributions</v-toolbar-title>
<v-btn-toggle v-model="distriTool" multiple density="compact" rounded="false" variant="text" color="primary"
class="mx-2">
<v-btn icon="md:fullscreen" value="fullwidth"></v-btn>
<v-btn icon="mdi-math-log" value="log"></v-btn>
</v-btn-toggle>
</v-toolbar>
<v-row align="start" class="mb-2">
<v-col :cols="fullWidth ? 12 : 6">
<v-card flat class="my-3">
<v-card-title>Systems </v-card-title>
<v-card-text>
<PlotFigure :options="unref(computedDistriSystemOptions)" defer></PlotFigure>
</v-card-text>
</v-card>
</v-col>
<v-col :cols="fullWidth ? 12 : 6">
<v-card flat>
<v-card-title>Taxonomic</v-card-title>
<v-card-text>
<v-select v-model="selectedTaxoRank" :items="availableTaxo" density="compact"
label="Select taxonomic rank"></v-select>
<PlotFigure defer :options="unref(computedDistriTaxoOptions)"></PlotFigure>
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-card>
<ServerDbTable title="RefSeq" db="refseq" :sortBy="sortBy" :headers="computedHeaders" :item-value="itemValue"
:facets="facets">
</ServerDbTable>
</template>
\ No newline at end of file
<script setup lang="ts">
import { useFacetsStore } from '~~/stores/facets'
const sortBy: Ref<{ key: string, order: string }[]> = ref([{ key: 'system', order: "asc" }])
const itemValue = ref("id");
const facets = ref(["system", "completed",
"plddts",])
const headers: Ref<Object[]> = ref([
{ title: "System", key: "system" },
{ title: "Proteins in structure", key: 'proteins_in_the_prediction', sortable: false },
{ title: "System genes", key: "system_genes", sortable: false },
{ title: "Completed", key: "completed" },
{ title: "Predition type", key: "prediction_type" },
{ title: "Num of genes", key: "system_number_of_genes" },
{ title: "pLDDT", key: "plddts" },
{ title: "iptm+ptm", key: "iptm+ptm" },
{ title: "pDockQ", key: "pDockQ" },
{ title: "Type", key: "type" }
])
</script>
<template>
<ServerDbTable title="Predicted Strucutres" db="structure" :sortBy="sortBy" :headers="headers" :item-value="itemValue"
:facets="facets">
</ServerDbTable>
</template>
\ No newline at end of file
......@@ -2,17 +2,19 @@ import { useArticlesStore, type CslJson } from '../stores/articles'
import { ref, computed, watchEffect, toValue } from "vue"
// import { useFetch } from '#app';
// import { useFetch } from "nuxt"
import { useFetch } from '#imports'
import { type MaybeRef, useFetch } from '#imports'
import article from "@/public/articles.json"
export interface ArticleMessage {
export interface CrossrefArticle {
DOI: string;
issue: number;
title: string | string[];
type: string;
title: string[];
author: Array<{ family: string; given: string }>;
"container-title-short": string;
// "container-title-short": string;
"short-container-title": string;
"container-title": string;
abstract: string;
abstract?: string;
published: {
"date-parts": string[];
};
......@@ -23,7 +25,7 @@ export interface ArticleMessage {
export interface Article {
export interface WikiArticle {
DOI: string
title: string
subtitle: string
......@@ -36,115 +38,125 @@ export interface Article {
prependIcon: string
}
export interface RawArticle {
message: ArticleMessage
message: CrossrefArticle
}
type SrcArticle = ArticleMessage | CslJson
type SrcArticle = CrossrefArticle | CslJson
export function useFetchArticle(doi: string) {
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)
const doiBaseUrl = ref(new URL("https://doi.org/"));
const url = ref(new URL(`/works/${doi}`, " https://api.crossref.org/").href);
const article = computed(() => {
if (srcArticle.value != undefined) {
const url = ref(new URL(`/works/${toValue(doi)}`, " https://api.crossref.org/").href);
const article = ref()
const zoteroArticles = ref()
function toAuthorsString(authors: Array<{ family: string; given: string }>) {
return authors
.map((curr) => {
return `${curr.family} ${curr.given}`;
})
.join(", ");
}
function getReferenceUrl(doi: string) {
return new URL(doi, doiBaseUrl.value).href;
}
function zoteroArticleToArticle(zoteroArticle: CslJson) {
if (zoteroArticle != undefined) {
const {
DOI,
title,
"container-title-short": cts,
"container-title": ct,
journalAbbreviation,
abstract,
published,
issued,
author,
...rest
} = srcArticle.value;
let sanitizedAbstract = abstract
if (sanitizedAbstract) {
sanitizedAbstract = /(?:\<jats\:p\>)?(.*)(?:\<\/jats\:p\>)?/.exec(sanitizedAbstract)?.[1] ?? ''
}
const sanitizedTitle = (Array.isArray(title)) ? title[0] : title
const sanitizedContainerTitle = (Array.isArray(ct)) ? cts?.length > 0 ? cts[0] : ct?.length > 0 ? ct[0] : "" : journalAbbreviation || ct
} = zoteroArticle;
return {
DOI,
title: sanitizedTitle,
title,
subtitle: toAuthorsString(author || []),
author,
containerTitle: sanitizedContainerTitle,
abstract: sanitizedAbstract,
year: published?.["date-parts"][0][0] ?? issued?.["date-parts"][0][0] ?? '',
containerTitle: ct,
abstract,
year: issued?.["date-parts"][0][0] ?? '',
href: getReferenceUrl(DOI),
target: "_blank",
prependIcon: "mdi-newspaper-variant-outline",
}
} else { return srcArticle.value }
})
const zoteroArticles = ref([])
// const config = useRuntimeConfig()
// console.log(config.value)
const fetchLocalArticles = () => {
useFetch<RawArticle[]>(
"/articles.json",
{ lazy: true, server: false }
).then(({ data }) => {
zoteroArticles.value = data.value
}) // localPending.value = articlesPending.value
if (zoteroArticles.value?.length > 0) {
for (const article of zoteroArticles.value) {
// console.log("article files : ", article.DOI)
store.add(article)
}
}
}
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
})
}
watchEffect(() => {
// no article in the store
if (store.articles.size === 0) {
fetchLocalArticles()
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 (store.articles.has(doi)) {
srcArticle.value = store.articles.get(doi)
return
} else {
fetchCrossRef()
}
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
})
function toAuthorsString(authors: Array<{ family: string; given: string }>) {
return authors
.map((curr) => {
return `${curr.family} ${curr.given}`;
})
.join(", ");
}
}
function getReferenceUrl(doi: string) {
return new URL(doi, doiBaseUrl.value).href;
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 }
}
......
import { MeiliSearch } from 'meilisearch'
import { useRuntimeConfig, watchEffect, type MaybeRef, ref, toValue } from '#imports'
import type { FacetDistribution, Hits } from 'meilisearch';
export function useFetchMsDocument(
import { useAsyncState } from '@vueuse/core'
import { errorMonitor } from 'events';
export async function useFetchMsDocument(
index: MaybeRef<string> = ref(""),
search: Ref<string> = ref(""),
filter: Ref<string> = ref(''),
......@@ -18,12 +21,11 @@ export function useFetchMsDocument(
apiKey: runtimeConfig.public.meiliApiKey
})
const pending = ref(false)
const filterError = ref(null)
const filterError: Ref<string | null> = ref(null)
const hits: Ref<Hits<Record<string, any>>> = ref([])
const totalHits = ref(0)
const totalPages = ref(0)
const facetDistribution: Ref<FacetDistribution | undefined> = ref({})
// reset page when filter and search change
watch(filter, () => {
page.value = 1
......@@ -32,34 +34,36 @@ export function useFetchMsDocument(
page.value = 1
})
watchEffect(async () => {
try {
pending.value = true
const res = await client
.index(toValue(index))
.search(toValue(search), {
limit: toValue(limit),
filter: toValue(filter),
hitsPerPage: toValue(hitsPerPage),
page: toValue(page),
facets: toValue(facets),
sort: toValue(sort),
})
filterError.value = null
const { hits: resHits, totalHits: resTotalHits, totalPages: resTotalPages, facetDistribution: facetD } = res
totalHits.value = resTotalHits
hits.value = resHits
totalPages.value = resTotalPages
facetDistribution.value = facetD
} catch ({ code, message }) {
if (code === 'invalid_search_filter') {
filterError.value = message
}
} finally {
pending.value = false
}
})
try {
pending.value = true
console.log(pending.value)
const res = await client
.index(toValue(index))
.search(toValue(search), {
limit: toValue(limit),
filter: toValue(filter),
hitsPerPage: toValue(hitsPerPage),
page: toValue(page),
facets: toValue(facets),
sort: toValue(sort),
})
filterError.value = null
const { hits: resHits, totalHits: resTotalHits, totalPages: resTotalPages, facetDistribution: facetD } = res
totalHits.value = resTotalHits
hits.value = resHits
totalPages.value = resTotalPages
facetDistribution.value = facetD
pending.value = false
} catch (e: any) {
filterError.value = e
}
finally {
pending.value = false
}
// })
console.log(hits)
return { hits, totalHits, pending, filterError, totalPages, facetDistribution }
}
---
layout: db
navigation: false
---
\ No newline at end of file
---
::refseq-db
::
---
layout: db
navigation: false
---
::structure-db
::
\ No newline at end of file
......@@ -425,6 +425,45 @@
]
}
},
{
"id": "15342854/42JKZL7K",
"type": "article-journal",
"title": "A NON HEREDITARY, HOST-INDUCED VARIATION OF BACTERIAL VIRUSES",
"container-title": "Journal of Bacteriology",
"page": "557-569",
"volume": "64",
"issue": "4",
"URL": "https://journals.asm.org/doi/10.1128/jb.64.4.557-569.1952",
"DOI": "10.1128/jb.64.4.557-569.1952",
"journalAbbreviation": "J Bacteriol",
"language": "en",
"author": [
{
"family": "Luria",
"given": "S. E."
},
{
"family": "Human",
"given": "Mary L."
}
],
"issued": {
"date-parts": [
[
"1952"
]
]
},
"accessed": {
"date-parts": [
[
2023,
11,
21
]
]
}
},
{
"id": "15342854/NUNPK7GW",
"type": "article-journal",
......@@ -825,18 +864,18 @@
}
},
{
"id": "15342854/YQTB9FM9",
"id": "15342854/XWLAVRDY",
"type": "article-journal",
"title": "A widespread bacteriophage abortive infection system functions through a Type IV toxin-antitoxin mechanism",
"title": "A widespread bacteriophage abortive infection system functions through a Type IV toxin\u2013antitoxin mechanism",
"container-title": "Nucleic Acids Research",
"page": "4590-4605",
"volume": "42",
"issue": "7",
"abstract": "Bacterial abortive infection (Abi) systems are 'altruistic' cell death systems that are activated by phage infection and limit viral replication, thereby providing protection to the bacterial population. Here, we have used a novel approach of screening Abi systems as a tool to identify and characterize toxin-antitoxin (TA)-acting Abi systems. We show that AbiE systems are encoded by bicistronic operons and function via a non-interacting (Type IV) bacteriostatic TA mechanism. The abiE operon was negatively autoregulated by the antitoxin, AbiEi, a member of a widespread family of putative transcriptional regulators. AbiEi has an N-terminal winged-helix-turn-helix domain that is required for repression of abiE transcription, and an uncharacterized bi-functional C-terminal domain, which is necessary for transcriptional repression and sufficient for toxin neutralization. The cognate toxin, AbiEii, is a predicted nucleotidyltransferase (NTase) and member of the DNA polymerase \u03b2 family. AbiEii specifically bound GTP, and mutations in conserved NTase motifs (I-III) and a newly identified motif (IV), abolished GTP binding and subsequent toxicity. The AbiE systems can provide phage resistance and enable stabilization of mobile genetic elements, such as plasmids. Our study reveals molecular insights into the regulation and function of the widespread bi-functional AbiE Abi-TA systems and the biochemical properties of both toxin and antitoxin proteins.",
"abstract": "Bacterial abortive infection (Abi) systems are \u2018altruistic\u2019 cell death systems that are activated by phage infection and limit viral replication, thereby providing protection to the bacterial population. Here, we have used a novel approach of screening Abi systems as a tool to identify and characterize toxin\u2013antitoxin (TA)-acting Abi systems. We show that AbiE systems are encoded by bicistronic operons and function via a non-interacting (Type IV) bacteriostatic TA mechanism. The abiE operon was negatively autoregulated by the antitoxin, AbiEi, a member of a widespread family of putative transcriptional regulators. AbiEi has an N-terminal winged-helix-turn-helix domain that is required for repression of abiE transcription, and an uncharacterized bi-functional C-terminal domain, which is necessary for transcriptional repression and sufficient for toxin neutralization. The cognate toxin, AbiEii, is a predicted nucleotidyltransferase (NTase) and member of the DNA polymerase b family. AbiEii specifically bound GTP, and mutations in conserved NTase motifs (I-III) and a newly identified motif (IV), abolished GTP binding and subsequent toxicity. The AbiE systems can provide phage resistance and enable stabilization of mobile genetic elements, such as plasmids. Our study reveals molecular insights into the regulation and function of the widespread bi-functional AbiE Abi-TA systems and the biochemical properties of both toxin and antitoxin proteins.",
"URL": "https://academic.oup.com/nar/article/42/7/4590/2436634",
"DOI": "10.1093/nar/gkt1419",
"note": "PMID: 24465005\nPMCID: PMC3985639",
"journalAbbreviation": "Nucleic Acids Res",
"language": "eng",
"note": "tex.ids= Dy2014a\nPMCID: PMC3985639\nPMID: 24465005",
"language": "en",
"author": [
{
"family": "Dy",
......@@ -852,7 +891,7 @@
},
{
"family": "Salmond",
"given": "George P. C."
"given": "George P.C."
},
{
"family": "Fineran",
......@@ -863,7 +902,17 @@
"date-parts": [
[
2014,
4
4,
1
]
]
},
"accessed": {
"date-parts": [
[
2023,
8,
31
]
]
}
......@@ -2187,6 +2236,168 @@
]
}
},
{
"id": "15342854/KBU3L4WT",
"type": "article-journal",
"title": "Complete Sequence of the New Lactococcal Abortive Phage Resistance Gene abiO",
"container-title": "Journal of Dairy Science",
"page": "1483-1485",
"volume": "81",
"issue": "6",
"URL": "https://www.sciencedirect.com/science/article/pii/S0022030298757133",
"DOI": "10.3168/jds.S0022-0302(98)75713-3",
"journalAbbreviation": "Journal of Dairy Science",
"author": [
{
"family": "Prevots",
"given": "Fabien"
},
{
"family": "Ritzenthaler",
"given": "Paul"
}
],
"issued": {
"date-parts": [
[
1998,
6,
1
]
]
},
"accessed": {
"date-parts": [
[
2023,
11,
22
]
]
}
},
{
"id": "15342854/MX2D8C95",
"type": "article-journal",
"title": "Core defense hotspots within Pseudomonas aeruginosa are a consistent and rich source of anti-phage defense systems",
"container-title": "Nucleic Acids Research",
"page": "4995-5005",
"volume": "51",
"issue": "10",
"abstract": "Bacteria use a diverse arsenal of anti-phage immune systems, including CRISPR-Cas and restriction enzymes. Recent advances in anti-phage system discovery and annotation tools have unearthed many unique systems, often encoded in horizontally transferred defense islands, which can be horizontally transferred. Here, we developed Hidden Markov Models (HMMs) for defense systems and queried microbial genomes on the NCBI database. Out of the 30 species with &gt;200 completely sequenced genomes, our analysis found Pseudomonas aeruginosa exhibits the greatest diversity of anti-phage systems, as measured by Shannon entropy. Using network analysis to identify the common neighbors of anti-phage systems, we identified two core defense hotspot loci (cDHS1 and cDHS2). cDHS1 is up to 224 kb (median: 26 kb) with varied arrangements of more than 30 distinct immune systems across isolates, while cDHS2 has 24 distinct systems (median: 6 kb). Both cDHS regions are occupied in a majority of P. aeruginosa isolates. Most cDHS genes are of unknown function potentially representing new anti-phage systems, which we validated by identifying a novel anti-phage system (Shango) commonly encoded in cDHS1. Identifying core genes flanking immune islands could simplify immune system discovery and may represent popular landing spots for diverse MGEs carrying anti-phage systems.",
"URL": "https://doi.org/10.1093/nar/gkad317",
"DOI": "10.1093/nar/gkad317",
"journalAbbreviation": "Nucleic Acids Research",
"author": [
{
"family": "Johnson",
"given": "Matthew C"
},
{
"family": "Laderman",
"given": "Eric"
},
{
"family": "Huiting",
"given": "Erin"
},
{
"family": "Zhang",
"given": "Chi"
},
{
"family": "Davidson",
"given": "Alan"
},
{
"family": "Bondy-Denomy",
"given": "Joseph"
}
],
"issued": {
"date-parts": [
[
2023,
6,
9
]
]
},
"accessed": {
"date-parts": [
[
2023,
11,
27
]
]
}
},
{
"id": "15342854/D4ZQGIA3",
"type": "article-journal",
"title": "CRISPR Provides Acquired Resistance Against Viruses in Prokaryotes",
"container-title": "Science",
"page": "1709-1712",
"volume": "315",
"issue": "5819",
"abstract": "Clustered regularly interspaced short palindromic repeats (CRISPR) are a distinctive feature of the genomes of most Bacteria and Archaea and are thought to be involved in resistance to bacteriophages. We found that, after viral challenge, bacteria integrated new spacers derived from phage genomic sequences. Removal or addition of particular spacers modified the phage-resistance phenotype of the cell. Thus, CRISPR, together with associated cas genes, provided resistance against phages, and resistance specificity is determined by spacer-phage sequence similarity.",
"URL": "https://www.science.org/doi/full/10.1126/science.1138140",
"DOI": "10.1126/science.1138140",
"note": "Publisher: American Association for the Advancement of Science",
"author": [
{
"family": "Barrangou",
"given": "Rodolphe"
},
{
"family": "Fremaux",
"given": "Christophe"
},
{
"family": "Deveau",
"given": "H\u00e9l\u00e8ne"
},
{
"family": "Richards",
"given": "Melissa"
},
{
"family": "Boyaval",
"given": "Patrick"
},
{
"family": "Moineau",
"given": "Sylvain"
},
{
"family": "Romero",
"given": "Dennis A."
},
{
"family": "Horvath",
"given": "Philippe"
}
],
"issued": {
"date-parts": [
[
2007,
3,
23
]
]
},
"accessed": {
"date-parts": [
[
2023,
11,
16
]
]
}
},
{
"id": "15342854/7ZMD86BY",
"type": "article-journal",
......@@ -2430,6 +2641,54 @@
]
}
},
{
"id": "15342854/QT3LWYX4",
"type": "article-journal",
"title": "Defense Islands in Bacterial and Archaeal Genomes and Prediction of Novel Defense Systems",
"container-title": "Journal of Bacteriology",
"page": "6039-6056",
"volume": "193",
"issue": "21",
"abstract": "ABSTRACT\n The arms race between cellular life forms and viruses is a major driving force of evolution. A substantial fraction of bacterial and archaeal genomes is dedicated to antivirus defense. We analyzed the distribution of defense genes and typical mobilome components (such as viral and transposon genes) in bacterial and archaeal genomes and demonstrated statistically significant clustering of antivirus defense systems and mobile genes and elements in genomic islands. The defense islands are enriched in putative operons and contain numerous overrepresented gene families. A detailed sequence analysis of the proteins encoded by genes in these families shows that many of them are diverged variants of known defense system components, whereas others show features, such as characteristic operonic organization, that are suggestive of novel defense systems. Thus, genomic islands provide abundant material for the experimental study of bacterial and archaeal antivirus defense. Except for the CRISPR-Cas systems, different classes of defense systems, in particular toxin-antitoxin and restriction-modification systems, show nonrandom clustering in defense islands. It remains unclear to what extent these associations reflect functional cooperation between different defense systems and to what extent the islands are genomic \u201csinks\u201d that accumulate diverse nonessential genes, particularly those acquired via horizontal gene transfer. The characteristics of defense islands resemble those of mobilome islands. Defense and mobilome genes are nonrandomly associated in islands, suggesting nonadaptive evolution of the islands via a preferential attachment-like mechanism underpinned by the addictive properties of defense systems such as toxins-antitoxins and an important role of horizontal mobility in the evolution of these islands.",
"URL": "https://journals.asm.org/doi/10.1128/JB.05535-11",
"DOI": "10.1128/JB.05535-11",
"journalAbbreviation": "J Bacteriol",
"language": "en",
"author": [
{
"family": "Makarova",
"given": "Kira S."
},
{
"family": "Wolf",
"given": "Yuri I."
},
{
"family": "Snir",
"given": "Sagi"
},
{
"family": "Koonin",
"given": "Eugene V."
}
],
"issued": {
"date-parts": [
[
"2011"
]
]
},
"accessed": {
"date-parts": [
[
2023,
4,
5
]
]
}
},
{
"id": "15342854/LWM56CT7",
"type": "article-journal",
......@@ -3251,6 +3510,45 @@
]
}
},
{
"id": "15342854/YJPKUAZ2",
"type": "article-journal",
"title": "Host controlled variation in bacterial viruses",
"container-title": "Journal of Bacteriology",
"page": "113-121",
"volume": "65",
"issue": "2",
"URL": "https://journals.asm.org/doi/10.1128/jb.65.2.113-121.1953",
"DOI": "10.1128/jb.65.2.113-121.1953",
"note": "Publisher: American Society for Microbiology",
"author": [
{
"family": "Bertani",
"given": "G."
},
{
"family": "Weigle",
"given": "J. J."
}
],
"issued": {
"date-parts": [
[
1953,
2
]
]
},
"accessed": {
"date-parts": [
[
2023,
11,
21
]
]
}
},
{
"id": "15342854/J75JJ8W8",
"type": "article-journal",
......@@ -3363,6 +3661,55 @@
]
}
},
{
"id": "15342854/Y3G7W7UX",
"type": "article-journal",
"title": "Isolation, cloning and characterisation of the abiI gene from Lactococcus lactis subsp. lactis M138 encoding abortive phage infection",
"container-title": "Journal of Biotechnology",
"page": "95-104",
"volume": "54",
"issue": "2",
"abstract": "Plasmid pND852 (56 kb) encodes nisin resistance and was isolated from Lactococcus lactis ssp lactis (L. lactis) M138 by conjugation to L. lactis LM0230. It conferred strong resistance to the isometric-headed phage \u03c6712 and partial resistance to the prolate-headed phage \u03c6c2. A 2.6 kb HpaII fragment encoding phage resistance was cloned into the streptococcal/Bacillus hybrid vector pGB301 to generate pND817. The mechanism of phage resistance encoded by pND817 involved abortive infection and this was illustrated by a reduction in burst size from 166 to 6 at 30\u00b0C and from 160 to 90 at 37\u00b0C. Partial resistance was therefore retained at 37\u00b0C. DNA sequencing revealed that the abortive infection was encoded by a single open reading frame (ORF), designated abiI, encoding a 332 amino acid protein. Neither abiI nor the predicted product showed significant homology to any existing sequence in the GenBank database. Frame shift mutation at the unique EcoRI site within the ORF resulted in loss of the Abi+ phenotype, confirming that the ORF is responsible for the encoded phage resistance.",
"URL": "https://www.sciencedirect.com/science/article/pii/S0168165697016921",
"DOI": "10.1016/S0168-1656(97)01692-1",
"journalAbbreviation": "Journal of Biotechnology",
"author": [
{
"family": "Su",
"given": "Ping"
},
{
"family": "Harvey",
"given": "Melissa"
},
{
"family": "Im",
"given": "Hee J"
},
{
"family": "Dunn",
"given": "Noel W"
}
],
"issued": {
"date-parts": [
[
1997,
4,
25
]
]
},
"accessed": {
"date-parts": [
[
2023,
11,
22
]
]
}
},
{
"id": "15342854/2ENY7AF5",
"type": "article-journal",
......@@ -3711,7 +4058,7 @@
"issue": "4",
"abstract": "Abortive infection (Abi) systems, also called phage exclusion, block phage multiplication and cause premature bacterial cell death upon phage infection. This decreases the number of progeny particles and limits their spread to other cells allowing the bacterial population to survive. Twenty Abi systems have been isolated in Lactococcus lactis, a bacterium used in cheese-making fermentation processes, where phage attacks are of economical importance. Recent insights in their expression and mode of action indicate that, behind diverse phenotypic and molecular effects, lactococcal Abis share common traits with the well-studied Escherichia coli systems Lit and Prr. Abis are widespread in bacteria, and recent analysis indicates that Abis might have additional roles other than conferring phage resistance.",
"DOI": "10.1016/j.mib.2005.06.006",
"note": "PMID: 15979388",
"note": "tex.ids= Chopin2005\nPMID: 15979388",
"shortTitle": "Phage abortive infection in lactococci",
"journalAbbreviation": "Curr Opin Microbiol",
"language": "eng",
......@@ -4045,6 +4392,66 @@
]
}
},
{
"id": "15342854/IHZP7M6I",
"type": "article-journal",
"title": "Phenotypic and genetic characterization of the bacteriophage abortive infection mechanism AbiK from Lactococcus lactis",
"container-title": "Applied and Environmental Microbiology",
"page": "1274-1283",
"volume": "63",
"issue": "4",
"abstract": "The natural plasmid pSRQ800 isolated from Lactococcus lactis subsp. lactis W1 conferred strong phage resistance against small isometric phages of the 936 and P335 species when introduced into phage-sensitive L. lactis strains. It had very limited effect on prolate phages of the c2 species. The phage resistance mechanism encoded on pSRQ800 is a temperature-sensitive abortive infection system (Abi). Plasmid pSRQ800 was mapped, and the Abi genetic determinant was localized on a 4.5-kb EcoRI fragment. Cloning and sequencing of the 4.5-kb fragment allowed the identification of two large open reading frames. Deletion mutants showed that only orf1 was needed to produce the Abi phenotype. orf1 (renamed abiK) coded for a predicted protein of 599 amino acids (AbiK) with an estimated molecular size of 71.4 kDa and a pI of 7.98. DNA and protein sequence alignment programs found no significant homology with databases. However, a database query based on amino acid composition suggested that AbiK might be in the same protein family as AbiA. No phage DNA replication nor phage structural protein production was detected in infected AbiK+ L. lactis cells. This system is believed to act at or prior to phage DNA replication. WHen cloned into a high-copy vector, AbiK efficiency increased 100-fold. AbiK provides another powerful tool that can be useful in controlling phages during lactococcal fermentations.",
"URL": "https://journals.asm.org/doi/10.1128/aem.63.4.1274-1283.1997",
"DOI": "10.1128/aem.63.4.1274-1283.1997",
"note": "Publisher: American Society for Microbiology",
"author": [
{
"family": "Emond",
"given": "E"
},
{
"family": "Holler",
"given": "B J"
},
{
"family": "Boucher",
"given": "I"
},
{
"family": "Vandenbergh",
"given": "P A"
},
{
"family": "Vedamuthu",
"given": "E R"
},
{
"family": "Kondo",
"given": "J K"
},
{
"family": "Moineau",
"given": "S"
}
],
"issued": {
"date-parts": [
[
1997,
4
]
]
},
"accessed": {
"date-parts": [
[
2023,
11,
22
]
]
}
},
{
"id": "15342854/UXQPXABE",
"type": "article-journal",
......@@ -4340,7 +4747,7 @@
"abstract": "Viperin is an interferon-induced cellular protein that is conserved in animals1. It has\u00a0previously been shown to inhibit the replication of multiple viruses by producing the ribonucleotide 3\u2032-deoxy-3\u2032,4\u2032-didehydro (ddh)-cytidine triphosphate (ddhCTP), which acts as a chain terminator for viral RNA polymerase2. Here we show that eukaryotic viperin originated from a clade of bacterial and archaeal proteins that protect against phage infection. Prokaryotic viperins produce a set of modified ribonucleotides that include ddhCTP, ddh-guanosine triphosphate (ddhGTP) and ddh-uridine triphosphate (ddhUTP). We further show that prokaryotic viperins protect against T7 phage infection by inhibiting viral polymerase-dependent transcription, suggesting that it has an antiviral mechanism of action similar to that of animal viperin. Our results reveal a class of potential natural antiviral compounds produced by bacterial immune systems.",
"URL": "https://www.nature.com/articles/s41586-020-2762-2",
"DOI": "10.1038/s41586-020-2762-2",
"note": "Number: 7840\nPublisher: Nature Publishing Group",
"note": "tex.ids= Bernheim2021, Bernheim2021a\nnumber: 7840\npublisher: Nature Publishing Group",
"language": "en",
"author": [
{
......@@ -4455,7 +4862,7 @@
}
},
{
"id": "15342854/SGKZUETF",
"id": "15342854/KYSKHP5G",
"type": "article-journal",
"title": "Prophage-mediated defence against viral attack and viral counter-defence",
"container-title": "Nature Microbiology",
......@@ -4465,7 +4872,7 @@
"abstract": "Temperate phages are common, and prophages are abundant residents of sequenced bacterial genomes. Mycobacteriophages are viruses that infect mycobacterial hosts including Mycobacterium tuberculosis and Mycobacterium smegmatis, encompass substantial genetic diversity and are commonly temperate. Characterization of ten Cluster N temperate mycobacteriophages revealed at least five distinct prophage-expressed viral defence systems that interfere with the infection of lytic and temperate phages that are either closely related (homotypic defence) or unrelated (heterotypic defence) to the prophage. Target specificity is unpredictable, ranging from a single target phage to one-third of those tested. The defence systems include a single-subunit restriction system, a heterotypic exclusion system and a predicted (p)ppGpp synthetase, which blocks lytic phage growth, promotes bacterial survival and enables efficient lysogeny. The predicted (p)ppGpp synthetase coded by the Phrann prophage defends against phage Tweety infection, but Tweety codes for a tetrapeptide repeat protein, gp54, which acts as a highly effective counter-defence system. Prophage-mediated viral defence offers an efficient mechanism for bacterial success in host\u2013virus dynamics, and counter-defence promotes phage co-evolution.",
"URL": "https://www.nature.com/articles/nmicrobiol2016251",
"DOI": "10.1038/nmicrobiol.2016.251",
"note": "Number: 3\nPublisher: Nature Publishing Group",
"note": "tex.ids= Dedrick2017\nnumber: 3\npublisher: Nature Publishing Group",
"journalAbbreviation": "Nat Microbiol",
"language": "en",
"author": [
......@@ -4659,8 +5066,8 @@
"date-parts": [
[
2023,
1,
18
9,
27
]
]
}
......@@ -5374,6 +5781,50 @@
]
}
},
{
"id": "15342854/2WVG5C98",
"type": "article-journal",
"title": "Study of membrane attachment and in vivo co-localization of TerB protein from uropathogenic Escherichia coli KL53",
"container-title": "General Physiology and Biophysics",
"page": "286-292",
"volume": "30",
"issue": "3",
"abstract": "The tellurite resistance operon has been found in a wide range of bacteria. We have previously identi\ufb01ed the ter operon (terXYW and terZABCDEF) of the uropathogenic strain Escherichia coli KL53. In this study, we use an innovative approach to identify putative protein-protein interaction partners for one of the essential tellurite resistance proteins \u2013 TerB. We observe that N-terminus of TerB attaches to the periplasmic membrane, while the C-terminus is partly localized in the cytoplasm. Subsequently, by methods of in vivo cross-linking and mass-spectroscopic analysis, we have determined the proteins from both the membrane and cytoplasmic fractions, which can potentially interact with TerB.",
"URL": "http://www.elis.sk/index.php?page=shop.product_details&flypage=flypage.tpl&product_id=2468&category_id=78&option=com_virtuemart&Itemid=11",
"DOI": "10.4149/gpb_2011_03_286",
"journalAbbreviation": "gpb",
"language": "en",
"author": [
{
"family": "Alekhina",
"given": "O."
},
{
"family": "Valkovicova",
"given": "L."
},
{
"family": "Turna",
"given": "J."
}
],
"issued": {
"date-parts": [
[
2011
]
]
},
"accessed": {
"date-parts": [
[
2023,
11,
30
]
]
}
},
{
"id": "15342854/LDQLNJ9U",
"type": "article-journal",
......@@ -5384,7 +5835,7 @@
"issue": "6379",
"abstract": "The arms race between bacteria and phages led to the development of sophisticated antiphage defense systems, including CRISPR-Cas and restriction-modification systems. Evidence suggests that known and unknown defense systems are located in \"defense islands\" in microbial genomes. Here, we comprehensively characterized the bacterial defensive arsenal by examining gene families that are clustered next to known defense genes in prokaryotic genomes. Candidate defense systems were systematically engineered and validated in model bacteria for their antiphage activities. We report nine previously unknown antiphage systems and one antiplasmid system that are widespread in microbes and strongly protect against foreign invaders. These include systems that adopted components of the bacterial flagella and condensin complexes. Our data also suggest a common, ancient ancestry of innate immunity components shared between animals, plants, and bacteria.",
"DOI": "10.1126/science.aar4120",
"note": "PMID: 29371424\nPMCID: PMC6387622",
"note": "tex.ids= Doron2018, Doron2018a, Doron2018b\nPMCID: PMC6387622\nPMID: 29371424",
"journalAbbreviation": "Science",
"language": "eng",
"author": [
......
......@@ -25,6 +25,6 @@ appVersion: "1.16.0"
dependencies:
- name: meilisearch
version: 0.2.8
version: 0.3.0
repository: "https://meilisearch.github.io/meilisearch-kubernetes"
......@@ -7,7 +7,7 @@ services:
args:
BASE_URL: /wiki/
MEILI_HOST: http://localhost:7700
MEILI_API_KEY: 269d546c85959e3d125cf7b34975a91503e0a63f0547c395c06ddf696a7cf12d
MEILI_API_KEY: f9cc073016cbb392365aae86517878cb3f3408bb85c1fafd06e27f73ccb35e3d
container_name: nuxt
environment:
HOST: 0.0.0.0
......@@ -30,7 +30,7 @@ services:
- main
meilisearch:
image: getmeili/meilisearch:v1.4
image: getmeili/meilisearch:v1.5
# command:
# - meilisearch
# - --http-addr
......
<script setup lang="ts"></script>
<script setup lang="ts">
import { useArticlesStore } from '@/stores/articles'
const store = useArticlesStore()
const { data } = await useAsyncData('zotero-articles', async () => queryContent('_data/_articles').where({ _partial: true }).findOne())
if (data.value) {
const dataValue = toValue(data)?.body
for (const cslArticle of dataValue) {
if (cslArticle?.DOI) {
store.add(cslArticle)
}
}
}
</script>
<template>
<LayoutWrapper>
<slot />
......
......@@ -5,16 +5,7 @@ import { useFacetsStore, type Facets } from '~~/stores/facets'
const facetStore = useFacetsStore()
</script>
<template>
<LayoutWrapper :fluid="true" :toc="false" :edit="false">
<template #drawer="{ drawer }">
<v-navigation-drawer :model-value="drawer" :border="1" color="background">
<v-list>
<v-list-item v-for="(value, key) in facetStore.facets.facetDistribution" :key="key"
:title="key"></v-list-item>
</v-list>
</v-navigation-drawer>
</template>
<LayoutWrapper :fluid="true" :toc="false" :edit="false" :nav-drawer="false">
<slot />
</LayoutWrapper>
</template>
......
......@@ -6,6 +6,7 @@ export default defineNuxtConfig({
'vuetify-nuxt-module',
'@vueuse/nuxt',
'@pinia/nuxt',
'nuxt-meilisearch',
// '@unocss/nuxt',
],
content: {
......@@ -32,6 +33,11 @@ export default defineNuxtConfig({
}
},
meilisearch: {
hostUrl: 'https://my-meilisearch-server.example.com',
searchApiKey: 'api_key',
serverSideUsage: false // default false
},
devtools: {
enabled: false
},
......@@ -39,6 +45,10 @@ export default defineNuxtConfig({
public: {
defenseFinderWebservice: '/',
meilisearchClient: {
hostUrl: 'http://localhost:7700',
searchApiKey: 'api_key',
},
meiliHost: 'http://localhost:7700',
meiliApiKey: 'api_key'
}
......