Compare commits
2 Commits
4a50969892
...
9079893371
| Author | SHA1 | Date |
|---|---|---|
|
|
9079893371 | |
|
|
8c81b69888 |
|
|
@ -0,0 +1,79 @@
|
||||||
|
name: Deploy Search Typesense
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- staging
|
||||||
|
- production
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Route each branch to a specific directory and PM2 environment
|
||||||
|
- name: Setup Environment Variables
|
||||||
|
run: |
|
||||||
|
if [[ "${{ github.ref_name }}" == "production" ]]; then
|
||||||
|
TARGET="/var/www/search.carpa.com"
|
||||||
|
echo "PM2_ENV=production" >> $GITHUB_ENV
|
||||||
|
elif [[ "${{ github.ref_name }}" == "staging" ]]; then
|
||||||
|
TARGET="/var/www/dev.search.carpa.com"
|
||||||
|
echo "PM2_ENV=staging" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
|
||||||
|
PM2_NAME="search-${{ github.ref_name }}"
|
||||||
|
|
||||||
|
echo "TARGET_DIR=$TARGET" >> $GITHUB_ENV
|
||||||
|
echo "PM2_NAME=$PM2_NAME" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
echo "NUXT_PUBLIC_FEEDBACK_WEBHOOK=${{ secrets.NUXT_PUBLIC_FEEDBACK_WEBHOOK }}" >> $GITHUB_ENV
|
||||||
|
echo "NUXT_PUBLIC_RECAPTCHA_SITE_KEY=${{ secrets.NUXT_PUBLIC_RECAPTCHA_SITE_KEY }}" >> $GITHUB_ENV
|
||||||
|
echo "NUXT_PUBLIC_FEEDBACK_MAX_PER_HOUR=${{ secrets.NUXT_PUBLIC_FEEDBACK_MAX_PER_HOUR }}" >> $GITHUB_ENV
|
||||||
|
echo "NUXT_PUBLIC_FEEDBACK_MAX_PER_SESSION=${{ secrets.NUXT_PUBLIC_FEEDBACK_MAX_PER_SESSION }}" >> $GITHUB_ENV
|
||||||
|
echo "NUXT_PUBLIC_FEEDBACK_COOLDOWN_SEC=${{ secrets.NUXT_PUBLIC_FEEDBACK_COOLDOWN_SEC }}" >> $GITHUB_ENV
|
||||||
|
echo "NUXT_PUBLIC_FEEDBACK_MIN_SECONDS=${{ secrets.NUXT_PUBLIC_FEEDBACK_MIN_SECONDS }}" >> $GITHUB_ENV
|
||||||
|
echo "NUXT_FEEDBACK_TOKEN=${{ secrets.NUXT_FEEDBACK_TOKEN }}" >> $GITHUB_ENV
|
||||||
|
echo "NUXT_PUBLIC_TYPESENSE_URL=${{ secrets.NUXT_PUBLIC_TYPESENSE_URL }}" >> $GITHUB_ENV
|
||||||
|
echo "NUXT_PUBLIC_TYPESENSE_API_KEY=${{ secrets.NUXT_PUBLIC_TYPESENSE_API_KEY }}" >> $GITHUB_ENV
|
||||||
|
echo "NUXT_PUBLIC_ENTRELINEAS_DEV_KEY=${{ secrets.NUXT_PUBLIC_ENTRELINEAS_DEV_KEY }}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '24'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build Nuxt Application
|
||||||
|
run: npm run build
|
||||||
|
env:
|
||||||
|
NITRO_PRESET: node-server
|
||||||
|
|
||||||
|
- name: Copy Build Files to Vultr
|
||||||
|
uses: appleboy/scp-action@master
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.VPS_HOST }}
|
||||||
|
username: ${{ secrets.VPS_USER }}
|
||||||
|
key: ${{ secrets.VPS_SSH_KEY }}
|
||||||
|
port: 22
|
||||||
|
source: ".output/*,package.json,ecosystem.config.cjs"
|
||||||
|
target: "${{ env.TARGET_DIR }}"
|
||||||
|
|
||||||
|
- name: Restart PM2 on Vultr
|
||||||
|
uses: appleboy/ssh-action@master
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.VPS_HOST }}
|
||||||
|
username: ${{ secrets.VPS_USER }}
|
||||||
|
key: ${{ secrets.VPS_SSH_KEY }}
|
||||||
|
port: 22
|
||||||
|
script: |
|
||||||
|
export NVM_DIR="$HOME/.nvm"
|
||||||
|
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
||||||
|
cd ${{ env.TARGET_DIR }}
|
||||||
|
PM2_NAME=${{ env.PM2_NAME }} pm2 reload ${{ env.PM2_NAME }} --env ${{ env.PM2_ENV }} || \
|
||||||
|
PM2_NAME=${{ env.PM2_NAME }} pm2 start ecosystem.config.cjs --env ${{ env.PM2_ENV }}
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { breakpointsTailwind } from '@vueuse/core'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
activeCount?: number
|
||||||
|
title?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
withDefaults(defineProps<Props>(), {
|
||||||
|
activeCount: 0,
|
||||||
|
title: 'search.filters'
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
─── RESPONSIVE: un solo componente, dos renders ───
|
||||||
|
- Desktop (>lg): acordeón colapsable (#content dentro)
|
||||||
|
- Mobile (<lg): botón → USlideover (#content dentro del slide)
|
||||||
|
|
||||||
|
El slot #content es el MISMO en ambos casos
|
||||||
|
(el input + botón para agregar bible_study)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const breakpoints = useBreakpoints(breakpointsTailwind)
|
||||||
|
const isMobile = breakpoints.smaller('lg')
|
||||||
|
|
||||||
|
const isExpanded = ref(true)
|
||||||
|
const isSlideoverOpen = ref(false)
|
||||||
|
|
||||||
|
const { $i18n } = useNuxtApp()
|
||||||
|
const t = $i18n.t
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="border-b border-default">
|
||||||
|
<!-- ─── DESKTOP (>lg): ACORDEÓN COLAPSABLE ──────── -->
|
||||||
|
<div v-if="!isMobile" class="select-none">
|
||||||
|
<button
|
||||||
|
class="w-full flex items-center justify-between px-4 sm:px-6 py-2 text-sm font-medium text-muted hover:text-foreground transition-colors"
|
||||||
|
@click="isExpanded = !isExpanded"
|
||||||
|
>
|
||||||
|
<span class="flex items-center gap-2">
|
||||||
|
<UIcon name="i-lucide-filter" class="size-4" />
|
||||||
|
{{ t(title) }}
|
||||||
|
<UBadge v-if="activeCount > 0" size="sm" variant="solid" color="primary">
|
||||||
|
{{ activeCount }}
|
||||||
|
</UBadge>
|
||||||
|
</span>
|
||||||
|
<UIcon
|
||||||
|
:name="isExpanded ? 'i-lucide-chevron-up' : 'i-lucide-chevron-down'"
|
||||||
|
class="size-4 transition-transform"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<div v-show="isExpanded" class="px-4 sm:px-6 pb-3">
|
||||||
|
<slot name="content" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ─── MOBILE (<lg): BOTÓN + SLIDEOVER ─────────── -->
|
||||||
|
<div v-else class="px-4 sm:px-6 py-2">
|
||||||
|
<UButton
|
||||||
|
size="sm"
|
||||||
|
color="neutral"
|
||||||
|
variant="outline"
|
||||||
|
class="w-full justify-between"
|
||||||
|
@click="isSlideoverOpen = true"
|
||||||
|
>
|
||||||
|
<span class="flex items-center gap-2">
|
||||||
|
<UIcon name="i-lucide-filter" class="size-4" />
|
||||||
|
{{ t(title) }}
|
||||||
|
</span>
|
||||||
|
<span class="flex items-center gap-2">
|
||||||
|
<UBadge v-if="activeCount > 0" size="sm" variant="solid" color="primary">
|
||||||
|
{{ activeCount }}
|
||||||
|
</UBadge>
|
||||||
|
<UIcon name="i-lucide-chevron-right" class="size-4" />
|
||||||
|
</span>
|
||||||
|
</UButton>
|
||||||
|
|
||||||
|
<ClientOnly>
|
||||||
|
<USlideover v-model:open="isSlideoverOpen">
|
||||||
|
<template #content>
|
||||||
|
<div class="p-4 space-y-4">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<h3 class="text-lg font-semibold flex items-center gap-2">
|
||||||
|
<UIcon name="i-lucide-filter" class="size-5" />
|
||||||
|
{{ t(title) }}
|
||||||
|
</h3>
|
||||||
|
<UButton
|
||||||
|
icon="i-lucide-x"
|
||||||
|
size="sm"
|
||||||
|
color="neutral"
|
||||||
|
variant="ghost"
|
||||||
|
@click="isSlideoverOpen = false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<slot name="content" />
|
||||||
|
<div v-if="$slots.chips" class="pt-2">
|
||||||
|
<slot name="chips" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</USlideover>
|
||||||
|
</ClientOnly>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
import { computed, ref, watch, onMounted, onBeforeUnmount } from 'vue'
|
import { computed, ref, watch, onMounted, onBeforeUnmount } from 'vue'
|
||||||
import { breakpointsTailwind, useDebounce } from '@vueuse/core'
|
import { breakpointsTailwind, useDebounce } from '@vueuse/core'
|
||||||
import PublicationDetail from '~/components/PublicationDetail.vue'
|
import PublicationDetail from '~/components/PublicationDetail.vue'
|
||||||
|
import FiltersContainer from '~/components/searchPanel/FiltersContainer.vue'
|
||||||
import { useSettingsStore } from '~/stores/settings'
|
import { useSettingsStore } from '~/stores/settings'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -156,64 +157,15 @@ const sortMode = ref<'relevance' | 'date'>('relevance')
|
||||||
|
|
||||||
// ---- Filtro bible_study multi-chip (solo para Estudios) --------------------
|
// ---- Filtro bible_study multi-chip (solo para Estudios) --------------------
|
||||||
|
|
||||||
interface BibleStudyChip {
|
const {
|
||||||
id: number
|
activeBibleStudies,
|
||||||
title: string
|
activeCount,
|
||||||
}
|
bibleStudyInput,
|
||||||
|
isValidating,
|
||||||
const bibleStudyInput = ref<number | null>(null)
|
applyBibleStudyFilter,
|
||||||
const activeBibleStudies = ref<BibleStudyChip[]>([])
|
removeBibleStudyFilter,
|
||||||
const isValidating = ref(false)
|
clearAllBibleStudyFilters,
|
||||||
|
} = useFilters()
|
||||||
async function applyBibleStudyFilter() {
|
|
||||||
const val = bibleStudyInput.value
|
|
||||||
if (val === null || val <= 0) return
|
|
||||||
if (activeBibleStudies.value.some(bs => bs.id === val)) {
|
|
||||||
toast.add({ title: 'Estudio ya agregado', description: `El estudio #${val} ya está en el filtro`, color: 'info' })
|
|
||||||
bibleStudyInput.value = null
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
isValidating.value = true
|
|
||||||
try {
|
|
||||||
const res = await documentsApi.multiSearch({
|
|
||||||
multiSearchParameters: {},
|
|
||||||
multiSearchSearchesParameter: {
|
|
||||||
searches: [{
|
|
||||||
collection: props.mainCollection,
|
|
||||||
q: '*',
|
|
||||||
queryBy: 'title',
|
|
||||||
filterBy: `bible_study:=${val}`,
|
|
||||||
perPage: 1,
|
|
||||||
includeFields: 'bible_study,title'
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const hit = (res?.results?.[0] as { hits?: Array<{ document: { title?: string } }> })?.hits?.[0]
|
|
||||||
if (hit) {
|
|
||||||
activeBibleStudies.value = [...activeBibleStudies.value, { id: val, title: hit.document.title || '' }]
|
|
||||||
bibleStudyInput.value = null
|
|
||||||
refetchResults()
|
|
||||||
} else {
|
|
||||||
toast.add({ title: 'Estudio no encontrado', description: `No existe el estudio #${val}`, color: 'warning' })
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Error validando bible_study', err)
|
|
||||||
} finally {
|
|
||||||
isValidating.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeBibleStudyFilter(id: number) {
|
|
||||||
activeBibleStudies.value = activeBibleStudies.value.filter(bs => bs.id !== id)
|
|
||||||
refetchResults()
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearAllBibleStudyFilters() {
|
|
||||||
activeBibleStudies.value = []
|
|
||||||
refetchResults()
|
|
||||||
}
|
|
||||||
|
|
||||||
function refetchResults() {
|
function refetchResults() {
|
||||||
if (!debouncedQuery.value.trim()) {
|
if (!debouncedQuery.value.trim()) {
|
||||||
|
|
@ -226,6 +178,10 @@ function refetchResults() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(activeBibleStudies, () => {
|
||||||
|
refetchResults()
|
||||||
|
})
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
const groupedHits = ref<SearchGroup[]>([])
|
const groupedHits = ref<SearchGroup[]>([])
|
||||||
|
|
@ -690,6 +646,7 @@ function metaLocation(meta: DocMeta | undefined): string {
|
||||||
<span class="italic">{{ author }}</span>
|
<span class="italic">{{ author }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ─── BUSCADOR ─────────────────────────────────── -->
|
||||||
<div class="px-4 sm:px-6 py-3 border-b border-default flex items-center gap-2" id="inputField">
|
<div class="px-4 sm:px-6 py-3 border-b border-default flex items-center gap-2" id="inputField">
|
||||||
<UInput
|
<UInput
|
||||||
v-model="query"
|
v-model="query"
|
||||||
|
|
@ -715,41 +672,11 @@ function metaLocation(meta: DocMeta | undefined): string {
|
||||||
>{{ t('search.phrase') }}</button>
|
>{{ t('search.phrase') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="query.trim()" class="px-4 sm:px-6 py-3">
|
|
||||||
<USelect
|
|
||||||
v-model="sortMode"
|
|
||||||
:items="[
|
|
||||||
{ label: t('search.sort.relevance'), value: 'relevance' },
|
|
||||||
{ label: t('search.sort.date'), value: 'date' }
|
|
||||||
]"
|
|
||||||
size="sm"
|
|
||||||
class="shrink-0 min-w-[130px]"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="showBibleStudyFilter" class="px-4 sm:px-6 py-2 border-b border-default space-y-2">
|
<!-- ─── CHIPS ACTIVOS (solo desktop: fuera del FiltersContainer) ─── -->
|
||||||
<div class="flex items-center gap-2">
|
<!-- En desktop los chips van debajo del input; en mobile van DENTRO del slideover -->
|
||||||
<UInput
|
<div v-if="showBibleStudyFilter && activeBibleStudies.length > 0 && !isMobile" class="px-4 sm:px-6 py-2 border-b border-default">
|
||||||
v-model="bibleStudyInput"
|
<div class="flex flex-wrap items-center gap-1.5">
|
||||||
type="number"
|
|
||||||
min="1"
|
|
||||||
:placeholder="$t('search.bible_study_placeholder')"
|
|
||||||
size="sm"
|
|
||||||
class="w-36"
|
|
||||||
@keyup.enter="applyBibleStudyFilter"
|
|
||||||
/>
|
|
||||||
<UButton
|
|
||||||
size="sm"
|
|
||||||
color="neutral"
|
|
||||||
variant="outline"
|
|
||||||
:loading="isValidating"
|
|
||||||
:disabled="bibleStudyInput === null || bibleStudyInput <= 0"
|
|
||||||
@click="applyBibleStudyFilter"
|
|
||||||
>
|
|
||||||
+ {{ $t('search.filter') }}
|
|
||||||
</UButton>
|
|
||||||
</div>
|
|
||||||
<div v-if="activeBibleStudies.length > 0" class="flex flex-wrap items-center gap-1.5">
|
|
||||||
<UBadge
|
<UBadge
|
||||||
v-for="bs in activeBibleStudies"
|
v-for="bs in activeBibleStudies"
|
||||||
:key="bs.id"
|
:key="bs.id"
|
||||||
|
|
@ -780,6 +707,92 @@ function metaLocation(meta: DocMeta | undefined): string {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ─── SORT ────────────────────────────────────── -->
|
||||||
|
<div v-if="query.trim()" class="px-4 sm:px-6 py-3 flex items-center gap-2">
|
||||||
|
<p class="text-sm">Busqueda: </p>
|
||||||
|
<USelect
|
||||||
|
v-model="sortMode"
|
||||||
|
:items="[
|
||||||
|
{ label: t('search.sort.relevance'), value: 'relevance' },
|
||||||
|
{ label: t('search.sort.date'), value: 'date' }
|
||||||
|
]"
|
||||||
|
size="sm"
|
||||||
|
class="shrink-0 min-w-[130px]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ─── FILTROS: AGREGAR NUEVOS (acordeón desktop / slideover mobile) ─── -->
|
||||||
|
<!--
|
||||||
|
El componente FiltersContainer renderiza DISTINTO según la pantalla:
|
||||||
|
- Desktop (>lg): acordeón colapsable con header "Filtros"
|
||||||
|
- Mobile (<lg): botón "Filtros" que abre un USlideover
|
||||||
|
El slot #content (input + botón) es el MISMO en ambos casos.
|
||||||
|
El slot #chips se usa solo en mobile (dentro del slideover debajo del content).
|
||||||
|
-->
|
||||||
|
<template v-if="showBibleStudyFilter">
|
||||||
|
<FiltersContainer
|
||||||
|
:active-count="activeBibleStudies.length"
|
||||||
|
title="search.filters"
|
||||||
|
>
|
||||||
|
<!-- #chips se renderiza dentro del slideover en mobile -->
|
||||||
|
<template #chips>
|
||||||
|
<div class="flex flex-wrap items-center gap-1.5">
|
||||||
|
<UBadge
|
||||||
|
v-for="bs in activeBibleStudies"
|
||||||
|
:key="bs.id"
|
||||||
|
size="sm"
|
||||||
|
variant="subtle"
|
||||||
|
color="primary"
|
||||||
|
class="max-w-full"
|
||||||
|
>
|
||||||
|
<span class="truncate">{{ $t('search.bible_study_chip', { number: bs.id }) }}</span>
|
||||||
|
<UButton
|
||||||
|
icon="i-lucide-x"
|
||||||
|
size="2xs"
|
||||||
|
color="neutral"
|
||||||
|
variant="ghost"
|
||||||
|
class="ml-1 shrink-0"
|
||||||
|
@click="removeBibleStudyFilter(bs.id)"
|
||||||
|
/>
|
||||||
|
</UBadge>
|
||||||
|
<div v-if="activeBibleStudies.length > 0">
|
||||||
|
<UButton
|
||||||
|
size="sm"
|
||||||
|
color="neutral"
|
||||||
|
variant="ghost"
|
||||||
|
class="text-xs"
|
||||||
|
@click="clearAllBibleStudyFilters">
|
||||||
|
{{ $t('search.bible_study_clear') }}
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #content>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<UInput
|
||||||
|
v-model="bibleStudyInput"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
:placeholder="$t('search.bible_study_placeholder')"
|
||||||
|
size="sm"
|
||||||
|
class="w-36"
|
||||||
|
@keyup.enter="applyBibleStudyFilter(props.mainCollection)"
|
||||||
|
/>
|
||||||
|
<UButton
|
||||||
|
size="sm"
|
||||||
|
color="neutral"
|
||||||
|
variant="outline"
|
||||||
|
:loading="isValidating"
|
||||||
|
:disabled="bibleStudyInput === null || bibleStudyInput <= 0"
|
||||||
|
@click="applyBibleStudyFilter(props.mainCollection)"
|
||||||
|
>
|
||||||
|
+ {{ $t('search.filter') }}
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</FiltersContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
<UAlert
|
<UAlert
|
||||||
v-if="errorMsg"
|
v-if="errorMsg"
|
||||||
:title="errorMsg"
|
:title="errorMsg"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
interface BibleStudyChip {
|
||||||
|
id: number
|
||||||
|
title: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeBibleStudies = useState<BibleStudyChip[]>('bible-study-chips', () => [])
|
||||||
|
|
||||||
|
export function useFilters() {
|
||||||
|
const { documentsApi } = useTypesenseApi()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const bibleStudyInput = ref<number | null>(null)
|
||||||
|
const isValidating = ref(false)
|
||||||
|
|
||||||
|
const activeCount = computed(() => activeBibleStudies.value.length)
|
||||||
|
|
||||||
|
async function applyBibleStudyFilter(mainCollection: string) {
|
||||||
|
const val = bibleStudyInput.value
|
||||||
|
if (val === null || val <= 0) return
|
||||||
|
if (activeBibleStudies.value.some(bs => bs.id === val)) {
|
||||||
|
toast.add({ title: 'Estudio ya agregado', description: `El estudio #${val} ya está en el filtro`, color: 'info' })
|
||||||
|
bibleStudyInput.value = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isValidating.value = true
|
||||||
|
try {
|
||||||
|
const res = await documentsApi.multiSearch({
|
||||||
|
multiSearchParameters: {},
|
||||||
|
multiSearchSearchesParameter: {
|
||||||
|
searches: [{
|
||||||
|
collection: mainCollection,
|
||||||
|
q: '*',
|
||||||
|
queryBy: 'title',
|
||||||
|
filterBy: `bible_study:=${val}`,
|
||||||
|
perPage: 1,
|
||||||
|
includeFields: 'bible_study,title'
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const hit = (res?.results?.[0] as { hits?: Array<{ document: { title?: string } }> })?.hits?.[0]
|
||||||
|
if (hit) {
|
||||||
|
activeBibleStudies.value = [...activeBibleStudies.value, { id: val, title: hit.document.title || '' }]
|
||||||
|
bibleStudyInput.value = null
|
||||||
|
} else {
|
||||||
|
toast.add({ title: 'Estudio no encontrado', description: `No existe el estudio #${val}`, color: 'warning' })
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error validando bible_study', err)
|
||||||
|
} finally {
|
||||||
|
isValidating.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeBibleStudyFilter(id: number) {
|
||||||
|
activeBibleStudies.value = activeBibleStudies.value.filter(bs => bs.id !== id)
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearAllBibleStudyFilters() {
|
||||||
|
activeBibleStudies.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
activeBibleStudies,
|
||||||
|
activeCount,
|
||||||
|
bibleStudyInput,
|
||||||
|
isValidating,
|
||||||
|
applyBibleStudyFilter,
|
||||||
|
removeBibleStudyFilter,
|
||||||
|
clearAllBibleStudyFilters
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,141 +6,153 @@ export const typeConfig: Record<ChangeEntry['type'], { label: string; color: str
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChangeEntry {
|
export interface ChangeEntry {
|
||||||
type: 'nuevo' | 'mejora' | 'fix' | 'eliminado'
|
type: 'nuevo' | 'mejora' | 'fix' | 'eliminado'
|
||||||
text: string
|
text: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Release {
|
export interface Release {
|
||||||
version: string
|
version: string
|
||||||
date: string
|
date: string
|
||||||
title: string
|
title: string
|
||||||
description?: string
|
description?: string
|
||||||
changes: ChangeEntry[]
|
changes: ChangeEntry[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const releases: Release[] = [
|
export const releases: Release[] = [
|
||||||
{
|
{
|
||||||
version: '0.8',
|
version: '0.9',
|
||||||
date: '5 de junio, 2026',
|
date: '20 de julio, 2026',
|
||||||
title: 'Autoría visible en Estudios Bíblicos, Conferencias e Historial',
|
title: 'Filtro por N° de Estudio',
|
||||||
changes: [
|
changes: [
|
||||||
{ type: 'nuevo', text: 'Nombre del autor visible en el panel lateral de Estudios Bíblicos (Dr. José Benjamín Pérez Matos)' },
|
{ type: 'nuevo', text: 'Nuevo filtro por número de Estudio en la sección de Estudios: ingresá el número y se agrega como chip para buscar dentro de ese estudio' },
|
||||||
{ type: 'nuevo', text: 'Nombre del autor visible en el panel lateral de Conferencias (Dr. William Soto Santiago)' },
|
{ type: 'nuevo', text: 'Se pueden agregar varios estudios a la vez, cada uno como un chip individual' },
|
||||||
{ type: 'nuevo', text: 'Nombre del autor visible en el panel de detalle del documento para ambas secciones' },
|
{ type: 'nuevo', text: 'Los chips con los filtros activos se muestran siempre visibles debajo de la barra de búsqueda' },
|
||||||
{ type: 'nuevo', text: 'El historial muestra el nombre del autor en cada entrada de Estudios Bíblicos y Conferencias' },
|
{ type: 'mejora', text: 'En escritorio los filtros se agrupan en una sección colapsable para no ocupar espacio innecesario' },
|
||||||
{ type: 'nuevo', text: 'Se agregó al momento de hacer una búsqueda un desplegable con los resultados "Más recientes" y "Normal"' },
|
{ type: 'mejora', text: 'En móvil los filtros se configuran desde un panel deslizante para aprovechar mejor la pantalla' }
|
||||||
{ type: 'mejora', text: 'El panel de detalle abierto desde el historial también muestra el autor correspondiente' },
|
]
|
||||||
{ type: 'fix', text: 'Las colecciones en el historial ahora muestran "Estudios Bíblicos" y "Conferencias" en lugar de los identificadores internos' },
|
},
|
||||||
{ type: 'fix', text: 'Corrección de etiquetas en inglés para las pestañas del historial' }
|
{
|
||||||
]
|
version: '0.8',
|
||||||
},
|
date: '5 de junio, 2026',
|
||||||
{
|
title: 'Autoría visible en Estudios Bíblicos, Conferencias e Historial',
|
||||||
version: '0.7',
|
changes: [
|
||||||
date: '31 de mayo, 2026 11:50PM',
|
{ type: 'nuevo', text: 'Nombre del autor visible en el panel lateral de Estudios Bíblicos (Dr. José Benjamín Pérez Matos)' },
|
||||||
title: 'Tour y optimizaciones',
|
{ type: 'nuevo', text: 'Nombre del autor visible en el panel lateral de Conferencias (Dr. William Soto Santiago)' },
|
||||||
changes: [
|
{ type: 'nuevo', text: 'Nombre del autor visible en el panel de detalle del documento para ambas secciones' },
|
||||||
{ type: 'nuevo', text: 'Agregado tour virtual con localización para explicar funcionamiento del buscador.'},
|
{ type: 'nuevo', text: 'El historial muestra el nombre del autor en cada entrada de Estudios Bíblicos y Conferencias' },
|
||||||
{ type: 'nuevo', text: '_Agregada página de inicio que muestra la versión más reciente del changelog.' },
|
{ type: 'nuevo', text: 'Se agregó al momento de hacer una búsqueda un desplegable con los resultados "Más recientes" y "Normal"' },
|
||||||
{ type: 'mejora', text: 'Separación de changelog a un TS aparte, para utilizar en changelog y en el home sin duplicación de código.'},
|
{ type: 'mejora', text: 'El panel de detalle abierto desde el historial también muestra el autor correspondiente' },
|
||||||
{ type: 'nuevo', text: 'Finalizado flow de automatizacion entre backend y typesense'}
|
{ type: 'fix', text: 'Las colecciones en el historial ahora muestran "Estudios Bíblicos" y "Conferencias" en lugar de los identificadores internos' },
|
||||||
]
|
{ type: 'fix', text: 'Corrección de etiquetas en inglés para las pestañas del historial' }
|
||||||
},
|
]
|
||||||
{
|
},
|
||||||
version: '0.6',
|
{
|
||||||
date: '31 de mayo, 2026',
|
version: '0.7',
|
||||||
title: 'Feedback con traducciones, bloqueo de Entrelíneas y acceso desarrollador',
|
date: '31 de mayo, 2026 11:50PM',
|
||||||
changes: [
|
title: 'Tour y optimizaciones',
|
||||||
{ type: 'nuevo', text: 'Página de Feedback con traducciones completas en 4 idiomas' },
|
changes: [
|
||||||
{ type: 'nuevo', text: 'Sistema de bloqueo por clave de desarrollador para secciones en desarrollo' },
|
{ type: 'nuevo', text: 'Agregado tour virtual con localización para explicar funcionamiento del buscador.'},
|
||||||
{ type: 'nuevo', text: 'Acceso de desarrollador en Configuración con desbloqueo por clave' },
|
{ type: 'nuevo', text: '_Agregada página de inicio que muestra la versión más reciente del changelog.' },
|
||||||
{ type: 'nuevo', text: 'Banner visual mejorado para Entrelíneas cuando está bloqueado' },
|
{ type: 'mejora', text: 'Separación de changelog a un TS aparte, para utilizar en changelog y en el home sin duplicación de código.'},
|
||||||
{ type: 'mejora', text: 'Traducciones añadidas al componente BugReportInput' },
|
{ type: 'nuevo', text: 'Finalizado flow de automatizacion entre backend y typesense'}
|
||||||
{ type: 'mejora', text: 'Nombre del tab Feedback ahora usa traducciones' },
|
]
|
||||||
{ type: 'mejora', text: 'Textos del banner de Entrelíneas traducidos a 4 idiomas' }
|
},
|
||||||
]
|
{
|
||||||
},
|
version: '0.6',
|
||||||
{
|
date: '31 de mayo, 2026',
|
||||||
version: '0.5',
|
title: 'Feedback con traducciones, bloqueo de Entrelíneas y acceso desarrollador',
|
||||||
date: '30 de mayo, 2026',
|
changes: [
|
||||||
title: 'Soporte de HTML en Entrelíneas',
|
{ type: 'nuevo', text: 'Página de Feedback con traducciones completas en 4 idiomas' },
|
||||||
description: 'El visor de Entrelíneas ahora renderiza contenido en HTML además de texto plano.',
|
{ type: 'nuevo', text: 'Sistema de bloqueo por clave de desarrollador para secciones en desarrollo' },
|
||||||
changes: [
|
{ type: 'nuevo', text: 'Acceso de desarrollador en Configuración con desbloqueo por clave' },
|
||||||
{ type: 'nuevo', text: 'Renderizado de contenido HTML en el detalle de Entrelíneas' },
|
{ type: 'nuevo', text: 'Banner visual mejorado para Entrelíneas cuando está bloqueado' },
|
||||||
{ type: 'mejora', text: 'Los fragmentos HTML se muestran con formato original preservado' },
|
{ type: 'mejora', text: 'Traducciones añadidas al componente BugReportInput' },
|
||||||
{ type: 'fix', text: 'Corrección de errores de sintaxis en el componente de detalle' },
|
{ type: 'mejora', text: 'Nombre del tab Feedback ahora usa traducciones' },
|
||||||
{ type: 'mejora', text: 'El historial ahora muestra correctamente documentos de Entrelíneas con HTML' }
|
{ type: 'mejora', text: 'Textos del banner de Entrelíneas traducidos a 4 idiomas' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
version: '0.4',
|
version: '0.5',
|
||||||
date: '26 de mayo, 2026',
|
date: '30 de mayo, 2026',
|
||||||
title: 'Mejoras de interfaz y exploracion',
|
title: 'Soporte de HTML en Entrelíneas',
|
||||||
changes: [
|
description: 'El visor de Entrelíneas ahora renderiza contenido en HTML además de texto plano.',
|
||||||
{ type: 'mejora', text: 'El código del documento aparece visible en la pantalla de detalle' },
|
changes: [
|
||||||
{ type: 'mejora', text: 'El badge "Borrador" solo se muestra cuando el documento es borrador' },
|
{ type: 'nuevo', text: 'Renderizado de contenido HTML en el detalle de Entrelíneas' },
|
||||||
{ type: 'mejora', text: 'Tooltip mejorado al copiar párrafos al portapapeles' },
|
{ type: 'mejora', text: 'Los fragmentos HTML se muestran con formato original preservado' },
|
||||||
{ type: 'mejora', text: 'El explorador filtra resultados mostrando solo ítems con párrafos' },
|
{ type: 'fix', text: 'Corrección de errores de sintaxis en el componente de detalle' },
|
||||||
{ type: 'mejora', text: 'Panel lateral preparado para notas y resaltados (próximamente)' },
|
{ type: 'mejora', text: 'El historial ahora muestra correctamente documentos de Entrelíneas con HTML' }
|
||||||
{ type: 'fix', text: 'Posicionamiento del panel lateral corregido en documentos cortos' },
|
]
|
||||||
{ type: 'fix', text: 'Corrección de traducciones en francés' },
|
},
|
||||||
{ type: 'fix', text: 'Ajustes visuales en múltiples vistas' }
|
{
|
||||||
]
|
version: '0.4',
|
||||||
},
|
date: '26 de mayo, 2026',
|
||||||
{
|
title: 'Mejoras de interfaz y exploracion',
|
||||||
version: '0.3',
|
changes: [
|
||||||
date: '25 de mayo, 2026',
|
{ type: 'mejora', text: 'El código del documento aparece visible en la pantalla de detalle' },
|
||||||
title: 'Portapapeles y corrección de bugs',
|
{ type: 'mejora', text: 'El badge "Borrador" solo se muestra cuando el documento es borrador' },
|
||||||
changes: [
|
{ type: 'mejora', text: 'Tooltip mejorado al copiar párrafos al portapapeles' },
|
||||||
{ type: 'nuevo', text: 'Panel de copiado mejorado al seleccionar párrafos' },
|
{ type: 'mejora', text: 'El explorador filtra resultados mostrando solo ítems con párrafos' },
|
||||||
{ type: 'mejora', text: 'Función de copiar al portapapeles más robusta e independiente' },
|
{ type: 'mejora', text: 'Panel lateral preparado para notas y resaltados (próximamente)' },
|
||||||
{ type: 'fix', text: 'Corrección de bug al guardar y mostrar favoritos' },
|
{ type: 'fix', text: 'Posicionamiento del panel lateral corregido en documentos cortos' },
|
||||||
{ type: 'fix', text: 'Corrección de bug en la carga de referencias' },
|
{ type: 'fix', text: 'Corrección de traducciones en francés' },
|
||||||
{ type: 'fix', text: 'Corrección de bug en el detalle de publicaciones de referencia' }
|
{ type: 'fix', text: 'Ajustes visuales en múltiples vistas' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
version: '0.2',
|
version: '0.3',
|
||||||
date: '21–24 de mayo, 2026',
|
date: '25 de mayo, 2026',
|
||||||
title: 'Conferencias, copiar párrafos y estabilidad',
|
title: 'Portapapeles y corrección de bugs',
|
||||||
changes: [
|
changes: [
|
||||||
{ type: 'nuevo', text: 'Soporte de documentos de Conferencias y Actividades' },
|
{ type: 'nuevo', text: 'Panel de copiado mejorado al seleccionar párrafos' },
|
||||||
{ type: 'nuevo', text: 'Clic en párrafo para copiarlo directamente al portapapeles' },
|
{ type: 'mejora', text: 'Función de copiar al portapapeles más robusta e independiente' },
|
||||||
{ type: 'nuevo', text: 'Zoom de imagen en el visor de Entrelíneas' },
|
{ type: 'fix', text: 'Corrección de bug al guardar y mostrar favoritos' },
|
||||||
{ type: 'mejora', text: 'Número de párrafo más discreto en el detalle del documento' },
|
{ type: 'fix', text: 'Corrección de bug en la carga de referencias' },
|
||||||
{ type: 'mejora', text: 'Publicación del documento visible en todas las vistas y traducida' },
|
{ type: 'fix', text: 'Corrección de bug en el detalle de publicaciones de referencia' }
|
||||||
{ type: 'mejora', text: 'Corrección de estilos visuales para contenido antiguo' },
|
]
|
||||||
{ type: 'fix', text: 'Corrección de bug en favoritos (detalle)' },
|
},
|
||||||
{ type: 'fix', text: 'Badge "Borrador" agregado en lista y detalle de Estudios Bíblicos' },
|
{
|
||||||
{ type: 'fix', text: 'Rutas de navegación migradas correctamente a nuevo motor de búsqueda' },
|
version: '0.2',
|
||||||
{ type: 'fix', text: 'Corrección de scroll infinito en móvil para Entrelíneas' }
|
date: '21–24 de mayo, 2026',
|
||||||
]
|
title: 'Conferencias, copiar párrafos y estabilidad',
|
||||||
},
|
changes: [
|
||||||
{
|
{ type: 'nuevo', text: 'Soporte de documentos de Conferencias y Actividades' },
|
||||||
version: '0.1',
|
{ type: 'nuevo', text: 'Clic en párrafo para copiarlo directamente al portapapeles' },
|
||||||
date: '10–12 de mayo, 2026',
|
{ type: 'nuevo', text: 'Zoom de imagen en el visor de Entrelíneas' },
|
||||||
title: 'Historial, Favoritos y multilengua',
|
{ type: 'mejora', text: 'Número de párrafo más discreto en el detalle del documento' },
|
||||||
description: 'Primera versión funcional con las secciones principales activas.',
|
{ type: 'mejora', text: 'Publicación del documento visible en todas las vistas y traducida' },
|
||||||
changes: [
|
{ type: 'mejora', text: 'Corrección de estilos visuales para contenido antiguo' },
|
||||||
{ type: 'nuevo', text: 'Historial de búsqueda y exploración' },
|
{ type: 'fix', text: 'Corrección de bug en favoritos (detalle)' },
|
||||||
{ type: 'nuevo', text: 'Favoritos: guarda y accede a documentos desde Mi Listado' },
|
{ type: 'fix', text: 'Badge "Borrador" agregado en lista y detalle de Estudios Bíblicos' },
|
||||||
{ type: 'nuevo', text: 'Selector de idioma: Español, English, Français, Português' },
|
{ type: 'fix', text: 'Rutas de navegación migradas correctamente a nuevo motor de búsqueda' },
|
||||||
{ type: 'nuevo', text: 'Cantidad aproximada de resultados visible en los listados' },
|
{ type: 'fix', text: 'Corrección de scroll infinito en móvil para Entrelíneas' }
|
||||||
{ type: 'mejora', text: 'Paginación configurable: scroll infinito o páginas numeradas' },
|
]
|
||||||
{ type: 'mejora', text: 'Ajuste de cantidad de resultados por página en Configuración' },
|
},
|
||||||
{ type: 'fix', text: 'Corrección de traducciones en el menú principal' },
|
{
|
||||||
{ type: 'fix', text: 'Ajustes de colores y badges en listas de resultados' }
|
version: '0.1',
|
||||||
]
|
date: '10–12 de mayo, 2026',
|
||||||
},
|
title: 'Historial, Favoritos y multilengua',
|
||||||
{
|
description: 'Primera versión funcional con las secciones principales activas.',
|
||||||
version: '0.0.1',
|
changes: [
|
||||||
date: '7 de mayo, 2026',
|
{ type: 'nuevo', text: 'Historial de búsqueda y exploración' },
|
||||||
title: 'Lanzamiento inicial',
|
{ type: 'nuevo', text: 'Favoritos: guarda y accede a documentos desde Mi Listado' },
|
||||||
description: 'Primera versión del buscador con Estudios Bíblicos y Entrelíneas.',
|
{ type: 'nuevo', text: 'Selector de idioma: Español, English, Français, Português' },
|
||||||
changes: [
|
{ type: 'nuevo', text: 'Cantidad aproximada de resultados visible en los listados' },
|
||||||
{ type: 'nuevo', text: 'Búsqueda en Estudios Bíblicos' },
|
{ type: 'mejora', text: 'Paginación configurable: scroll infinito o páginas numeradas' },
|
||||||
{ type: 'nuevo', text: 'Visor de Entrelíneas con imagen y texto' },
|
{ type: 'mejora', text: 'Ajuste de cantidad de resultados por página en Configuración' },
|
||||||
{ type: 'nuevo', text: 'Búsqueda por palabras o por frase exacta' },
|
{ type: 'fix', text: 'Corrección de traducciones en el menú principal' },
|
||||||
{ type: 'nuevo', text: 'Estructura base de la aplicación' }
|
{ type: 'fix', text: 'Ajustes de colores y badges en listas de resultados' }
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
]
|
{
|
||||||
|
version: '0.0.1',
|
||||||
|
date: '7 de mayo, 2026',
|
||||||
|
title: 'Lanzamiento inicial',
|
||||||
|
description: 'Primera versión del buscador con Estudios Bíblicos y Entrelíneas.',
|
||||||
|
changes: [
|
||||||
|
{ type: 'nuevo', text: 'Búsqueda en Estudios Bíblicos' },
|
||||||
|
{ type: 'nuevo', text: 'Visor de Entrelíneas con imagen y texto' },
|
||||||
|
{ type: 'nuevo', text: 'Búsqueda por palabras o por frase exacta' },
|
||||||
|
{ type: 'nuevo', text: 'Estructura base de la aplicación' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
module.exports = {
|
||||||
|
apps: [
|
||||||
|
{
|
||||||
|
// You can append the PM2_ENV dynamically in the script if you want unique names in the PM2 list,
|
||||||
|
// or rely on the directory path to keep them separate.
|
||||||
|
name: process.env.PM2_NAME || 'SearchTs',
|
||||||
|
// port: '3000',
|
||||||
|
// exec_mode: 'cluster',
|
||||||
|
// instances: 'max',
|
||||||
|
script: './.output/server/index.mjs',
|
||||||
|
|
||||||
|
// Triggered by 'staging' branch
|
||||||
|
env_staging: {
|
||||||
|
NODE_ENV: 'staging',
|
||||||
|
PORT: 3005
|
||||||
|
},
|
||||||
|
|
||||||
|
// Triggered by 'production' branch
|
||||||
|
env_production: {
|
||||||
|
NODE_ENV: 'production',
|
||||||
|
PORT: 3010
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
@ -23,6 +23,7 @@
|
||||||
"bible_study_placeholder": "Study no...",
|
"bible_study_placeholder": "Study no...",
|
||||||
"bible_study_chip": "Study #{number} {title}",
|
"bible_study_chip": "Study #{number} {title}",
|
||||||
"bible_study_clear": "Clear filters",
|
"bible_study_clear": "Clear filters",
|
||||||
|
"filters": "Filters",
|
||||||
"placeholder": "Search for...",
|
"placeholder": "Search for...",
|
||||||
"searching": "Searching...",
|
"searching": "Searching...",
|
||||||
"tip": "Tip: wrap in \"quotes\" for exact phrase in that order.",
|
"tip": "Tip: wrap in \"quotes\" for exact phrase in that order.",
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@
|
||||||
"bible_study_placeholder": "N° de estudio...",
|
"bible_study_placeholder": "N° de estudio...",
|
||||||
"bible_study_chip": "Estudio #{number} {title}",
|
"bible_study_chip": "Estudio #{number} {title}",
|
||||||
"bible_study_clear": "Limpiar filtros",
|
"bible_study_clear": "Limpiar filtros",
|
||||||
|
"filters": "Filtros",
|
||||||
"words_tooltip": "Buscar por palabras",
|
"words_tooltip": "Buscar por palabras",
|
||||||
"phrases_tooltip": "Buscar por frases",
|
"phrases_tooltip": "Buscar por frases",
|
||||||
"instructions": "Selecciona un resultado de búsqueda...",
|
"instructions": "Selecciona un resultado de búsqueda...",
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@
|
||||||
"bible_study_placeholder": "N° d'étude...",
|
"bible_study_placeholder": "N° d'étude...",
|
||||||
"bible_study_chip": "Étude #{number} {title}",
|
"bible_study_chip": "Étude #{number} {title}",
|
||||||
"bible_study_clear": "Effacer les filtres",
|
"bible_study_clear": "Effacer les filtres",
|
||||||
|
"filters": "Filtres",
|
||||||
"word": "Mot",
|
"word": "Mot",
|
||||||
"phrase": "Phrase",
|
"phrase": "Phrase",
|
||||||
"placeholder": "Rechercher des activités",
|
"placeholder": "Rechercher des activités",
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@
|
||||||
"bible_study_placeholder": "N° do estudo...",
|
"bible_study_placeholder": "N° do estudo...",
|
||||||
"bible_study_chip": "Estudo #{number} {title}",
|
"bible_study_chip": "Estudo #{number} {title}",
|
||||||
"bible_study_clear": "Limpar filtros",
|
"bible_study_clear": "Limpar filtros",
|
||||||
|
"filters": "Filtros",
|
||||||
"word": "Palavra",
|
"word": "Palavra",
|
||||||
"phrase": "Frase",
|
"phrase": "Frase",
|
||||||
"placeholder": "Digite para pesquisar...",
|
"placeholder": "Digite para pesquisar...",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue