128 lines
3.1 KiB
Vue
128 lines
3.1 KiB
Vue
<template>
|
|
<div class="blog-list">
|
|
<section class="blog-list__hero surface-card">
|
|
<div>
|
|
<span class="blog-list__kicker">Community-Blogs</span>
|
|
<h1>Blogs</h1>
|
|
<p>Artikel, Projektstände und persönliche Einblicke aus der YourPart-Community.</p>
|
|
</div>
|
|
<div class="toolbar">
|
|
<router-link v-if="$store.getters.isLoggedIn" class="btn" to="/blogs/create">Neuen Blog erstellen</router-link>
|
|
</div>
|
|
</section>
|
|
|
|
<div v-if="loading" class="blog-list__state surface-card">Laden…</div>
|
|
<div v-else-if="!blogs.length" class="blog-list__state surface-card">Keine Blogs gefunden.</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">von {{ b.owner?.username || 'Unbekannt' }}</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)">Zum Blog</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 || 'Öffentliche Einträge, Gedanken und Projektstände aus der Community.';
|
|
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>
|