Files
yourpart3/frontend/src/views/blog/BlogListView.vue
Torsten Schulz (local) c6caeefb5f
All checks were successful
Deploy to production / deploy (push) Successful in 2m47s
feat(bisaya-course): enhance German course content and localization support
- Updated the create-german-for-bisaya-course-content.js script to improve lesson pattern retrieval by introducing a new function for generating a lesson pattern pool.
- Added new exercises for various topics including 'Wohnung & Nachbarn', 'Besuch empfangen', 'Arzt, Apotheke, Termin', and 'Amt, Dokumente, Anmeldung', enhancing practical language skills for learners.
- Improved localization by integrating translation keys for various UI elements and error messages across multiple components, ensuring a consistent user experience in both German and Bisaya.
- Enhanced the main.js file to recognize Bisaya language preferences in browser settings, improving accessibility for users.
2026-03-31 17:40:03 +02:00

128 lines
3.1 KiB
Vue

<template>
<div class="blog-list">
<section class="blog-list__hero surface-card">
<div>
<span class="blog-list__kicker">{{ $t('blog.list.eyebrow') }}</span>
<h1>{{ $t('blog.list.title') }}</h1>
<p>{{ $t('blog.list.intro') }}</p>
</div>
<div class="toolbar">
<router-link v-if="$store.getters.isLoggedIn" class="btn" to="/blogs/create">{{ $t('blog.list.create') }}</router-link>
</div>
</section>
<div v-if="loading" class="blog-list__state surface-card">{{ $t('blog.list.loading') }}</div>
<div v-else-if="!blogs.length" class="blog-list__state surface-card">{{ $t('blog.list.empty') }}</div>
<div v-else class="blog-grid">
<article v-for="b in blogs" :key="b.id" class="blog-card surface-card">
<div class="blog-card__meta">{{ $t('blog.list.by') }} {{ b.owner?.username || $t('blog.list.unknownAuthor') }}</div>
<h2><router-link :to="blogUrl(b)">{{ b.title }}</router-link></h2>
<p>{{ blogExcerpt(b) }}</p>
<router-link class="blog-card__link" :to="blogUrl(b)">{{ $t('blog.list.open') }}</router-link>
</article>
</div>
</div>
</template>
<script>
import { listBlogs } from '@/api/blogApi.js';
import { createBlogSlug } from '@/utils/seo.js';
export default {
name: 'BlogListView',
data: () => ({ blogs: [], loading: true }),
async mounted() {
try { this.blogs = await listBlogs(); } finally { this.loading = false; }
},
methods: {
blogUrl(blog) {
const slug = createBlogSlug(blog?.owner?.username, blog?.title);
return slug ? `/blogs/${encodeURIComponent(slug)}` : `/blogs/${blog.id}`;
},
blogExcerpt(blog) {
const source = blog?.description || this.$t('blog.list.fallbackExcerpt');
return source.length > 150 ? `${source.slice(0, 147)}...` : source;
},
},
}
</script>
<style scoped>
.blog-list {
max-width: var(--content-max-width);
margin: 0 auto;
padding-bottom: 24px;
}
.blog-list__hero {
display: flex;
align-items: end;
justify-content: space-between;
gap: 20px;
padding: 24px 26px;
margin-bottom: 18px;
}
.blog-list__kicker {
display: inline-block;
margin-bottom: 10px;
padding: 4px 10px;
border-radius: 999px;
background: rgba(120, 195, 138, 0.14);
color: #42634e;
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
}
.blog-list__hero p {
margin: 0;
color: var(--color-text-secondary);
}
.blog-list__state {
padding: 26px;
text-align: center;
color: var(--color-text-secondary);
}
.blog-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 18px;
}
.blog-card {
padding: 22px;
}
.blog-card__meta {
margin-bottom: 10px;
color: var(--color-text-muted);
font-size: 0.82rem;
font-weight: 600;
}
.blog-card h2 {
margin-bottom: 10px;
font-size: 1.3rem;
}
.blog-card p {
margin-bottom: 16px;
color: var(--color-text-secondary);
}
.blog-card__link {
color: var(--color-primary);
font-weight: 700;
}
@media (max-width: 960px) {
.blog-list__hero {
flex-direction: column;
align-items: flex-start;
}
}
</style>