feat: Refactor search functionality and integrate WordPress as Headless CMS
- Introduced a unified search service (`searchService.js`) for texts and blog posts. - Redesigned the search page (`Texts.vue`) with improved filters and pagination. - Enhanced navigation bar (`NavigationBar.vue`) for better user experience. - Added WordPress integration for news articles with a dedicated service (`wordpressService.js`). - Created a new About page (`AboutPage.vue`) detailing the association's history and values. - Updated backend API to support new endpoints and optimizations. - Implemented caching and performance improvements for search queries.
This commit is contained in:
@@ -3,51 +3,87 @@
|
||||
<div class="max-w-6xl mx-auto px-4">
|
||||
<!-- En-tête avec recherche -->
|
||||
<div class="mb-8">
|
||||
<h1 class="text-4xl font-bold text-black mb-4">Collection de Textes</h1>
|
||||
<h1 class="text-4xl font-bold text-black mb-4">Recherche</h1>
|
||||
<p class="text-gray-600 mb-6">
|
||||
Explorez notre collection de textes en patois franco-provençal
|
||||
Explorez notre collection de textes en patois et nos articles de blog
|
||||
</p>
|
||||
|
||||
<!-- Barre de recherche avancée -->
|
||||
<div class="bg-gray-50 rounded-lg p-6 mb-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
@input="performSearch"
|
||||
type="text"
|
||||
placeholder="Rechercher dans les textes..."
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-black"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<select
|
||||
v-model="selectedCategory"
|
||||
@change="performSearch"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-black"
|
||||
<!-- Barre de recherche principale -->
|
||||
<div class="mb-4">
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
@input="performSearch"
|
||||
type="text"
|
||||
placeholder="Rechercher dans les textes et articles..."
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-black"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Filtres par type de contenu -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Type de contenu</label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button
|
||||
@click="setContentType('all')"
|
||||
:class="contentType === 'all' ? 'bg-black text-white' : 'bg-white text-gray-700 border border-gray-300'"
|
||||
class="px-4 py-2 rounded-lg hover:shadow-sm transition-shadow"
|
||||
>
|
||||
<option value="">Toutes les catégories</option>
|
||||
<option v-for="category in categories" :key="category" :value="category">
|
||||
{{ category }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<select
|
||||
v-model="selectedDifficulty"
|
||||
@change="performSearch"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-black"
|
||||
Tout
|
||||
</button>
|
||||
<button
|
||||
@click="setContentType('texts')"
|
||||
:class="contentType === 'texts' ? 'bg-black text-white' : 'bg-white text-gray-700 border border-gray-300'"
|
||||
class="px-4 py-2 rounded-lg hover:shadow-sm transition-shadow"
|
||||
>
|
||||
<option value="">Toutes les difficultés</option>
|
||||
<option value="Facile">Facile</option>
|
||||
<option value="Moyen">Moyen</option>
|
||||
<option value="Difficile">Difficile</option>
|
||||
</select>
|
||||
Textes en patois
|
||||
</button>
|
||||
<button
|
||||
@click="setContentType('blog')"
|
||||
:class="contentType === 'blog' ? 'bg-black text-white' : 'bg-white text-gray-700 border border-gray-300'"
|
||||
class="px-4 py-2 rounded-lg hover:shadow-sm transition-shadow"
|
||||
>
|
||||
Articles de blog
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filtres supplémentaires -->
|
||||
<div class="flex flex-wrap gap-4 mt-4">
|
||||
<!-- Filtres pour textes -->
|
||||
<div v-if="contentType === 'texts' || contentType === 'all'" class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Catégories de textes</label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button
|
||||
v-for="category in textCategories"
|
||||
:key="category"
|
||||
@click="toggleTextCategory(category)"
|
||||
:class="selectedTextCategories.includes(category) ? 'bg-blue-600 text-white' : 'bg-white text-gray-700 border border-gray-300'"
|
||||
class="px-3 py-1 rounded-full text-sm hover:shadow-sm transition-shadow capitalize"
|
||||
>
|
||||
{{ category }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filtres pour blog -->
|
||||
<div v-if="contentType === 'blog' || contentType === 'all'" class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Catégories de blog</label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button
|
||||
v-for="category in blogCategories"
|
||||
:key="category.id"
|
||||
@click="toggleBlogCategory(category.id)"
|
||||
:class="selectedBlogCategories.includes(category.id) ? 'bg-green-600 text-white' : 'bg-white text-gray-700 border border-gray-300'"
|
||||
class="px-3 py-1 rounded-full text-sm hover:shadow-sm transition-shadow"
|
||||
>
|
||||
{{ category.name }}
|
||||
<span v-if="category.count" class="ml-1 opacity-75">({{ category.count }})</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filtres supplémentaires pour textes -->
|
||||
<div v-if="contentType === 'texts' || contentType === 'all'" class="flex flex-wrap gap-4">
|
||||
<label class="flex items-center">
|
||||
<input
|
||||
v-model="onlyWithAudio"
|
||||
@@ -70,59 +106,73 @@
|
||||
<!-- Résultats -->
|
||||
<div class="mb-6">
|
||||
<p style="color: #6b7280" class="mb-6">
|
||||
{{ filteredTexts.length }} texte(s) trouvé(s)
|
||||
<span v-if="searchQuery || selectedCategory || selectedDifficulty || onlyWithAudio">
|
||||
pour vos critères de recherche
|
||||
<span v-if="loading" class="inline-flex items-center">
|
||||
<svg class="animate-spin h-4 w-4 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
Chargement...
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ totalResults }} résultat(s) trouvé(s)
|
||||
<span v-if="hasActiveFilters">
|
||||
pour vos critères de recherche
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Liste des textes -->
|
||||
<!-- Liste des résultats -->
|
||||
<div class="grid gap-6">
|
||||
<!-- Résultat texte -->
|
||||
<div
|
||||
v-for="text in paginatedTexts"
|
||||
:key="text.id"
|
||||
v-for="item in paginatedResults"
|
||||
:key="`${item.type}-${item.id}`"
|
||||
class="bg-white border border-gray-200 rounded-lg p-6 hover:shadow-md transition-shadow cursor-pointer"
|
||||
@click="$router.push(`/texte/${text.id}`)"
|
||||
@click="navigateToItem(item)"
|
||||
>
|
||||
<div class="flex justify-between items-start">
|
||||
<!-- Badge de type -->
|
||||
<div class="mb-2">
|
||||
<span
|
||||
:class="item.type === 'text' ? 'bg-blue-100 text-blue-800' : 'bg-green-100 text-green-800'"
|
||||
class="px-2 py-1 rounded-full text-xs font-medium"
|
||||
>
|
||||
{{ item.type === 'text' ? 'Texte en patois' : 'Article de blog' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Contenu pour texte -->
|
||||
<div v-if="item.type === 'text'" class="flex justify-between items-start">
|
||||
<div class="flex-1">
|
||||
<h2 class="text-xl font-semibold text-black mb-2">
|
||||
{{ text.metadata.titre_fr }}
|
||||
{{ item.metadata.titre_fr }}
|
||||
</h2>
|
||||
<p class="text-lg mb-3" style="color: #6b7280">
|
||||
{{ text.metadata.titre_pt }}
|
||||
{{ item.metadata.titre_pt }}
|
||||
</p>
|
||||
|
||||
<div class="flex flex-wrap gap-4 text-sm mb-4" style="color: #6b7280">
|
||||
<span v-if="text.metadata.auteur">
|
||||
<strong>Auteur:</strong> {{ text.metadata.auteur }}
|
||||
<span v-if="item.metadata.auteur">
|
||||
<strong>Auteur:</strong> {{ item.metadata.auteur }}
|
||||
</span>
|
||||
<span v-if="text.metadata.traducteur">
|
||||
<strong>Traducteur:</strong> {{ text.metadata.traducteur }}
|
||||
<span v-if="item.metadata.traducteur">
|
||||
<strong>Traducteur:</strong> {{ item.metadata.traducteur }}
|
||||
</span>
|
||||
<span v-if="text.metadata.categorie">
|
||||
<strong>Catégorie:</strong> {{ text.metadata.categorie }}
|
||||
<span v-if="item.metadata.categorie" class="capitalize">
|
||||
<strong>Catégorie:</strong> {{ item.metadata.categorie }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Aperçu du texte -->
|
||||
<p class="text-sm line-clamp-3" style="color: #6b7280">
|
||||
{{ getTextPreview(text.frenchText) }}
|
||||
{{ getTextPreview(item.frenchText) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-end ml-4">
|
||||
<!-- Badges -->
|
||||
<div class="flex flex-col gap-2">
|
||||
<span
|
||||
v-if="text.metadata.difficulte"
|
||||
class="px-2 py-1 rounded-full text-xs"
|
||||
:class="getDifficultyClass(text.metadata.difficulte)"
|
||||
>
|
||||
{{ text.metadata.difficulte }}
|
||||
</span>
|
||||
<span v-if="text.hasAudio" class="flex items-center text-xs" style="color: #6b7280">
|
||||
<span v-if="item.hasAudio" class="flex items-center text-xs" style="color: #6b7280">
|
||||
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M18 3a1 1 0 00-1.196-.98l-10 2A1 1 0 006 5v6.114A4.369 4.369 0 005 11c-1.657 0-3 .895-3 2s1.343 2 3 2 3-.895 3-2V7.82l8-1.6v5.894A4.369 4.369 0 0015 12c-1.657 0-3 .895-3 2s1.343 2 3 2 3-.895 3-2V3z"/>
|
||||
</svg>
|
||||
@@ -131,6 +181,32 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contenu pour post -->
|
||||
<div v-else class="flex justify-between items-start">
|
||||
<div class="flex-1">
|
||||
<h2 class="text-xl font-semibold text-black mb-2" v-html="item.title"></h2>
|
||||
|
||||
<div class="flex flex-wrap gap-4 text-sm mb-4" style="color: #6b7280">
|
||||
<span v-if="item.author">
|
||||
<strong>Auteur:</strong> {{ item.author }}
|
||||
</span>
|
||||
<span v-if="item.date">
|
||||
<strong>Date:</strong> {{ formatDate(item.date) }}
|
||||
</span>
|
||||
<span v-if="item.categories && item.categories.length">
|
||||
<strong>Catégories:</strong> {{ item.categories.join(', ') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Aperçu du post -->
|
||||
<div class="text-sm line-clamp-3" style="color: #6b7280" v-html="item.excerpt"></div>
|
||||
</div>
|
||||
|
||||
<div v-if="item.featuredImage" class="ml-4">
|
||||
<img :src="item.featuredImage" :alt="item.featuredImageAlt" class="w-32 h-32 object-cover rounded-lg" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -160,11 +236,11 @@
|
||||
</div>
|
||||
|
||||
<!-- Message si aucun résultat -->
|
||||
<div v-if="filteredTexts.length === 0" class="text-center py-12">
|
||||
<div v-if="!loading && totalResults === 0" class="text-center py-12">
|
||||
<svg class="mx-auto h-12 w-12 text-gray-400 mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">Aucun texte trouvé</h3>
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">Aucun résultat trouvé</h3>
|
||||
<p class="text-gray-500">Essayez de modifier vos critères de recherche.</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -173,82 +249,177 @@
|
||||
|
||||
<script>
|
||||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { textService } from '../services/textService.js'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { searchService } from '../services/searchService.js'
|
||||
|
||||
export default {
|
||||
name: 'Texts',
|
||||
setup() {
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const allTexts = ref([])
|
||||
// État de la recherche
|
||||
const searchQuery = ref('')
|
||||
const selectedCategory = ref('')
|
||||
const selectedDifficulty = ref('')
|
||||
const contentType = ref('texts') // 'all', 'texts', 'blog' - Par défaut sur textes
|
||||
const selectedTextCategories = ref([])
|
||||
const selectedBlogCategories = ref([])
|
||||
const onlyWithAudio = ref(false)
|
||||
const currentPage = ref(1)
|
||||
const textsPerPage = 10
|
||||
const resultsPerPage = 10
|
||||
|
||||
const categories = computed(() => {
|
||||
const cats = new Set()
|
||||
allTexts.value.forEach(text => {
|
||||
if (text.metadata.categorie) {
|
||||
cats.add(text.metadata.categorie)
|
||||
}
|
||||
})
|
||||
return Array.from(cats).sort()
|
||||
// Résultats
|
||||
const searchResults = ref({ texts: [], posts: [], total: 0 })
|
||||
const textCategories = ref([])
|
||||
const blogCategories = ref([])
|
||||
const loading = ref(true) // Commencer en mode chargement
|
||||
let searchVersion = 0 // Pour éviter les race conditions
|
||||
|
||||
// Calculs
|
||||
const hasActiveFilters = computed(() => {
|
||||
return searchQuery.value.trim() !== '' ||
|
||||
selectedTextCategories.value.length > 0 ||
|
||||
selectedBlogCategories.value.length > 0 ||
|
||||
onlyWithAudio.value ||
|
||||
contentType.value !== 'all'
|
||||
})
|
||||
|
||||
const filteredTexts = computed(() => {
|
||||
// Ne pas filtrer localement - l'API backend s'en charge
|
||||
return allTexts.value
|
||||
const allResults = computed(() => {
|
||||
const results = []
|
||||
|
||||
if (contentType.value === 'all' || contentType.value === 'texts') {
|
||||
results.push(...searchResults.value.texts)
|
||||
}
|
||||
|
||||
if (contentType.value === 'all' || contentType.value === 'blog') {
|
||||
results.push(...searchResults.value.posts)
|
||||
}
|
||||
|
||||
return results
|
||||
})
|
||||
|
||||
const totalResults = computed(() => allResults.value.length)
|
||||
|
||||
const totalPages = computed(() => {
|
||||
return Math.ceil(filteredTexts.value.length / textsPerPage)
|
||||
return Math.ceil(totalResults.value / resultsPerPage)
|
||||
})
|
||||
|
||||
const paginatedTexts = computed(() => {
|
||||
const start = (currentPage.value - 1) * textsPerPage
|
||||
const end = start + textsPerPage
|
||||
return filteredTexts.value.slice(start, end)
|
||||
const paginatedResults = computed(() => {
|
||||
const start = (currentPage.value - 1) * resultsPerPage
|
||||
const end = start + resultsPerPage
|
||||
return allResults.value.slice(start, end)
|
||||
})
|
||||
|
||||
const loadTexts = async () => {
|
||||
// Méthodes
|
||||
const loadCategories = async () => {
|
||||
try {
|
||||
// Utiliser l'API de recherche avec les filtres actuels
|
||||
const texts = await textService.searchTexts(searchQuery.value, {
|
||||
category: selectedCategory.value,
|
||||
difficulty: selectedDifficulty.value,
|
||||
onlyWithAudio: onlyWithAudio.value
|
||||
})
|
||||
allTexts.value = texts
|
||||
const [textCats, blogCats] = await Promise.all([
|
||||
searchService.getTextCategories(),
|
||||
searchService.getBlogCategories()
|
||||
])
|
||||
textCategories.value = textCats
|
||||
blogCategories.value = blogCats
|
||||
} catch (error) {
|
||||
console.error('Erreur lors du chargement des textes:', error)
|
||||
// Fallback : charger tous les textes si la recherche échoue
|
||||
try {
|
||||
const allTextsData = await textService.loadAllTexts()
|
||||
allTexts.value = allTextsData
|
||||
} catch (fallbackError) {
|
||||
console.error('Erreur fallback:', fallbackError)
|
||||
allTexts.value = []
|
||||
}
|
||||
console.error('Erreur lors du chargement des catégories:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const performSearch = async () => {
|
||||
loading.value = true
|
||||
currentPage.value = 1
|
||||
await loadTexts()
|
||||
|
||||
// Incrémenter la version de recherche pour éviter les race conditions
|
||||
searchVersion++
|
||||
const currentVersion = searchVersion
|
||||
|
||||
try {
|
||||
const filters = {
|
||||
type: contentType.value,
|
||||
onlyWithAudio: onlyWithAudio.value
|
||||
}
|
||||
|
||||
// Ajouter les catégories de textes si sélectionnées
|
||||
if (selectedTextCategories.value.length > 0) {
|
||||
// Pour l'instant, on ne peut filtrer que par une catégorie via l'API
|
||||
// On prend la première sélectionnée
|
||||
filters.textCategory = selectedTextCategories.value[0]
|
||||
}
|
||||
|
||||
// Ajouter les catégories de blog si sélectionnées
|
||||
if (selectedBlogCategories.value.length > 0) {
|
||||
filters.blogCategories = selectedBlogCategories.value
|
||||
}
|
||||
|
||||
const results = await searchService.search(searchQuery.value, filters)
|
||||
|
||||
// Vérifier que cette recherche est toujours la plus récente
|
||||
if (currentVersion !== searchVersion) {
|
||||
console.log('Recherche obsolète ignorée')
|
||||
return
|
||||
}
|
||||
|
||||
// Filtrage côté client pour les catégories multiples de textes
|
||||
if (selectedTextCategories.value.length > 1) {
|
||||
results.texts = results.texts.filter(text =>
|
||||
selectedTextCategories.value.includes(text.metadata.categorie)
|
||||
)
|
||||
}
|
||||
|
||||
searchResults.value = results
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la recherche:', error)
|
||||
// Ne mettre à jour que si c'est toujours la recherche actuelle
|
||||
if (currentVersion === searchVersion) {
|
||||
searchResults.value = { texts: [], posts: [], total: 0 }
|
||||
}
|
||||
} finally {
|
||||
// Ne mettre loading à false que si c'est toujours la recherche actuelle
|
||||
if (currentVersion === searchVersion) {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const clearFilters = async () => {
|
||||
const setContentType = (type) => {
|
||||
contentType.value = type
|
||||
performSearch()
|
||||
}
|
||||
|
||||
const toggleTextCategory = (category) => {
|
||||
const index = selectedTextCategories.value.indexOf(category)
|
||||
if (index > -1) {
|
||||
selectedTextCategories.value.splice(index, 1)
|
||||
} else {
|
||||
selectedTextCategories.value.push(category)
|
||||
}
|
||||
performSearch()
|
||||
}
|
||||
|
||||
const toggleBlogCategory = (categoryId) => {
|
||||
const index = selectedBlogCategories.value.indexOf(categoryId)
|
||||
if (index > -1) {
|
||||
selectedBlogCategories.value.splice(index, 1)
|
||||
} else {
|
||||
selectedBlogCategories.value.push(categoryId)
|
||||
}
|
||||
performSearch()
|
||||
}
|
||||
|
||||
const clearFilters = () => {
|
||||
searchQuery.value = ''
|
||||
selectedCategory.value = ''
|
||||
selectedDifficulty.value = ''
|
||||
contentType.value = 'texts' // Remettre sur textes par défaut
|
||||
selectedTextCategories.value = []
|
||||
selectedBlogCategories.value = []
|
||||
onlyWithAudio.value = false
|
||||
currentPage.value = 1
|
||||
// IMPORTANT: Recharger via l'API après avoir vidé les filtres
|
||||
await loadTexts()
|
||||
performSearch()
|
||||
}
|
||||
|
||||
const navigateToItem = (item) => {
|
||||
if (item.type === 'text') {
|
||||
router.push(`/texte/${item.id}`)
|
||||
} else {
|
||||
router.push(`/actualites/${item.id}`)
|
||||
}
|
||||
}
|
||||
|
||||
const getTextPreview = (text) => {
|
||||
@@ -256,46 +427,61 @@ export default {
|
||||
return text.split('\n').slice(0, 3).join(' ').substring(0, 200) + '...'
|
||||
}
|
||||
|
||||
const getDifficultyClass = (difficulty) => {
|
||||
switch (difficulty?.toLowerCase()) {
|
||||
case 'facile':
|
||||
return 'bg-green-100 text-green-800'
|
||||
case 'moyen':
|
||||
case 'moyenne':
|
||||
return 'bg-yellow-100 text-yellow-800'
|
||||
case 'difficile':
|
||||
return 'bg-red-100 text-red-800'
|
||||
default:
|
||||
return 'bg-gray-100 text-gray-800'
|
||||
}
|
||||
const formatDate = (dateString) => {
|
||||
const date = new Date(dateString)
|
||||
return date.toLocaleDateString('fr-FR', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
})
|
||||
}
|
||||
|
||||
// Initialiser la recherche depuis l'URL
|
||||
watch(() => route.query.search, (newSearch) => {
|
||||
if (newSearch) {
|
||||
searchQuery.value = newSearch
|
||||
// Si recherche depuis la navbar, chercher dans tout
|
||||
contentType.value = 'all'
|
||||
performSearch()
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
onMounted(() => {
|
||||
loadTexts()
|
||||
onMounted(async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
await loadCategories()
|
||||
if (!route.query.search) {
|
||||
// Si pas de recherche URL, rester sur textes par défaut
|
||||
await performSearch()
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
allTexts,
|
||||
searchQuery,
|
||||
selectedCategory,
|
||||
selectedDifficulty,
|
||||
contentType,
|
||||
selectedTextCategories,
|
||||
selectedBlogCategories,
|
||||
onlyWithAudio,
|
||||
currentPage,
|
||||
categories,
|
||||
filteredTexts,
|
||||
textCategories,
|
||||
blogCategories,
|
||||
searchResults,
|
||||
loading,
|
||||
hasActiveFilters,
|
||||
totalResults,
|
||||
totalPages,
|
||||
paginatedTexts,
|
||||
paginatedResults,
|
||||
performSearch,
|
||||
setContentType,
|
||||
toggleTextCategory,
|
||||
toggleBlogCategory,
|
||||
clearFilters,
|
||||
navigateToItem,
|
||||
getTextPreview,
|
||||
getDifficultyClass
|
||||
formatDate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user