|
1 | 1 | <script setup lang="ts">
|
2 |
| -import { computed } from 'vue'; |
| 2 | +import { computed, onMounted, ref, watch } from 'vue'; |
3 | 3 | import { useHead } from '@vueuse/head'
|
4 | 4 | import { useRoute } from 'vue-router';
|
5 | 5 |
|
6 | 6 | import { useI18n } from 'vue-i18n';
|
7 | 7 | const { d } = useI18n();
|
8 | 8 |
|
| 9 | +interface SuggestedPost { |
| 10 | + path: string; |
| 11 | + title: string; |
| 12 | + cover?: string; |
| 13 | + description?: string; |
| 14 | +} |
| 15 | +
|
9 | 16 | const { frontmatter } = defineProps<{ frontmatter: any }>()
|
10 | 17 |
|
| 18 | +// Import all blog posts metadata |
| 19 | +const modules = import.meta.glob('/pages/blog/*.md') |
| 20 | +const blogPosts: Record<string, any> = {} |
| 21 | +
|
| 22 | +// Load all blog posts on mount |
| 23 | +onMounted(async () => { |
| 24 | + await Promise.all( |
| 25 | + Object.entries(modules).map(async ([path, loader]) => { |
| 26 | + const mod = await loader() as any |
| 27 | + blogPosts[path] = mod |
| 28 | + }) |
| 29 | + ) |
| 30 | +}) |
| 31 | +
|
| 32 | +// Add computed property for suggested posts paths |
| 33 | +const hasSuggestedPosts = computed(() => |
| 34 | + frontmatter.suggestedPosts && |
| 35 | + Array.isArray(frontmatter.suggestedPosts) && |
| 36 | + frontmatter.suggestedPosts.length > 0 |
| 37 | +) |
| 38 | +
|
| 39 | +// Initialize suggestedPostsData ref |
| 40 | +const suggestedPostsData = ref<SuggestedPost[]>([]) |
| 41 | +
|
| 42 | +// Watch for blog posts loading and update suggested posts |
| 43 | +watch(blogPosts, () => { |
| 44 | + if (!hasSuggestedPosts.value) return |
| 45 | +
|
| 46 | + try { |
| 47 | + const posts = frontmatter.suggestedPosts.map((path: string) => { |
| 48 | + const fullPath = `/pages${path}.md` |
| 49 | + const post = blogPosts[fullPath] |
| 50 | + return { |
| 51 | + path, |
| 52 | + title: post?.frontmatter?.title || path.split('/').pop()?.replace(/-/g, ' ') || 'Related Post', |
| 53 | + cover: post?.frontmatter?.cover, |
| 54 | + description: post?.frontmatter?.description |
| 55 | + } as SuggestedPost |
| 56 | + }) |
| 57 | + suggestedPostsData.value = posts |
| 58 | + } catch (error) { |
| 59 | + console.error('Error fetching suggested posts:', error) |
| 60 | + } |
| 61 | +}, { immediate: true }) |
| 62 | +
|
11 | 63 | useHead({
|
12 | 64 | title: 'SomosNLP - Democratizando el NLP en español',
|
13 | 65 | meta: [
|
@@ -117,6 +169,23 @@ const linkUrl = computed(() => `https://www.linkedin.com/sharing/share-offsite/?
|
117 | 169 | 🤗
|
118 | 170 | </IconButtonLink>
|
119 | 171 | </div>
|
| 172 | + <div v-if="hasSuggestedPosts && suggestedPostsData.length > 0" class="mt-12"> |
| 173 | + <hr class="mb-8" /> |
| 174 | + <h3 class="text-xl font-bold mb-6">Artículos relacionados</h3> |
| 175 | + <div class="grid grid-cols-1 md:grid-cols-2 gap-6"> |
| 176 | + <div v-for="post in suggestedPostsData" :key="post.path" |
| 177 | + class="border rounded-lg overflow-hidden hover:shadow-lg transition-shadow"> |
| 178 | + <RouterLink :to="post.path" class="block"> |
| 179 | + <img v-if="post.cover" :src="post.cover" :alt="post.title" |
| 180 | + class="w-full h-48 object-cover" /> |
| 181 | + <div class="p-4"> |
| 182 | + <h4 class="text-lg font-semibold">{{ post.title }}</h4> |
| 183 | + <p v-if="post.description" class="mt-2 text-sm text-gray-600">{{ post.description }}</p> |
| 184 | + </div> |
| 185 | + </RouterLink> |
| 186 | + </div> |
| 187 | + </div> |
| 188 | + </div> |
120 | 189 | <div v-if="$route.path.startsWith('/blog')" class="text-md text-center">
|
121 | 190 | <hr class="mt-8 mb-12" />
|
122 | 191 | <a target="_blank"
|
|
0 commit comments