fix: news frontend

This commit is contained in:
DecDuck
2025-03-10 12:05:10 +11:00
parent b6f52f660a
commit 1eede0f035
7 changed files with 53 additions and 62 deletions

View File

@@ -37,10 +37,10 @@
</div>
<div>
<label for="excerpt" class="block text-sm font-medium text-zinc-400">Excerpt</label>
<label for="excerpt" class="block text-sm font-medium text-zinc-400">Exercept</label>
<input
id="excerpt"
v-model="newArticle.excerpt"
v-model="newArticle.description"
type="text"
class="mt-1 block w-full rounded-md bg-zinc-900 border-zinc-700 text-zinc-100 shadow-sm focus:border-primary-500 focus:ring-primary-500"
required
@@ -171,7 +171,7 @@ const newTagInput = ref('');
const newArticle = ref({
title: '',
excerpt: '',
description: '',
content: '',
image: '',
tags: [] as string[]
@@ -290,7 +290,7 @@ const createArticle = async () => {
// Reset form
newArticle.value = {
title: '',
excerpt: '',
description: '',
content: '',
image: '',
tags: []

View File

@@ -87,7 +87,7 @@
</div>
<h3 class="relative text-sm font-medium text-zinc-100">{{ article.title }}</h3>
<p class="relative mt-1 text-xs text-zinc-400 line-clamp-2" v-html="formatExcerpt(article.excerpt)"></p>
<p class="relative mt-1 text-xs text-zinc-400 line-clamp-2" v-html="formatExcerpt(article.description)"></p>
<div class="relative mt-2 flex items-center gap-x-2 text-xs text-zinc-500">
<time :datetime="article.publishedAt">{{ formatDate(article.publishedAt) }}</time>
</div>
@@ -115,7 +115,7 @@ const availableTags = computed(() => {
if (!articles.value) return [];
const tags = new Set<string>();
articles.value.forEach(article => {
article.tags.forEach(tag => tags.add(tag));
article.tags.forEach(tag => tags.add(tag.name));
});
return Array.from(tags);
});
@@ -151,7 +151,7 @@ const filteredArticles = computed(() => {
return articles.value.filter((article) => {
const matchesSearch =
article.title.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
article.excerpt.toLowerCase().includes(searchQuery.value.toLowerCase());
article.description.toLowerCase().includes(searchQuery.value.toLowerCase());
const articleDate = new Date(article.publishedAt);
const now = new Date();
@@ -175,7 +175,7 @@ const filteredArticles = computed(() => {
}
const matchesTags = selectedTags.value.length === 0 ||
selectedTags.value.every(tag => article.tags.includes(tag));
selectedTags.value.every(tag => article.tags.find((e) => e.name == tag));
return matchesSearch && matchesDate && matchesTags;
});

View File

@@ -23,20 +23,20 @@ export const useNews = () => {
const create = async (article: {
title: string;
excerpt: string;
description: string;
content: string;
image?: string;
tags: string[];
authorId: string;
}) => {
return await $fetch('/api/v1/news', {
return await $fetch('/api/v1/admin/news', {
method: 'POST',
body: article
});
};
const remove = async (id: string) => {
return await $fetch(`/api/v1/news/${id}`, {
return await $fetch(`/api/v1/admin/news/${id}`, {
method: 'DELETE'
});
};

View File

@@ -1,5 +1,5 @@
<template>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div v-if="article" class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<!-- Banner header with blurred background -->
<div class="relative w-full h-[300px] mb-8 rounded-lg overflow-hidden">
<div class="absolute inset-0">
@@ -42,19 +42,19 @@
<div class="flex flex-col gap-y-3 sm:flex-row sm:items-center sm:gap-x-4 text-zinc-300">
<div class="flex items-center gap-x-4">
<time :datetime="article.publishedAt">{{ formatDate(article.publishedAt) }}</time>
<span class="text-blue-400">{{ article.author.displayName }}</span>
<span class="text-blue-400">{{ article.author?.displayName ?? "System" }}</span>
</div>
<div class="flex flex-wrap gap-2">
<span
v-for="tag in article.tags"
:key="tag"
:key="tag.id"
class="inline-flex items-center rounded-full bg-zinc-800/80 backdrop-blur-sm px-3 py-1 text-sm font-semibold text-zinc-100"
>
{{ tag }}
{{ tag.name }}
</span>
</div>
</div>
<p class="mt-4 text-lg text-zinc-300">{{ article.excerpt }}</p>
<p class="mt-4 text-lg text-zinc-300">{{ article.description }}</p>
</div>
</div>
</div>
@@ -88,7 +88,7 @@ if (!article.value) {
// Render markdown content
const renderedContent = computed(() => {
return micromark(article.value.content);
return micromark(article.value?.content ?? "");
});
const formatDate = (date: string) => {

View File

@@ -24,7 +24,7 @@
<NuxtLink
v-for="article in articles"
:key="article.id"
:to="`/news/article/${article.id}`"
:to="`/news/${article.id}`"
class="block"
>
<article
@@ -39,10 +39,10 @@
<div class="absolute top-4 left-4 flex gap-2">
<span
v-for="tag in article.tags"
:key="tag"
:key="tag.id"
class="inline-flex items-center rounded-full bg-zinc-900/75 px-3 py-1 text-sm font-semibold text-zinc-100 backdrop-blur"
>
{{ tag }}
{{ tag.name }}
</span>
</div>
</div>
@@ -53,14 +53,14 @@
<time :datetime="article.publishedAt" class="text-sm text-zinc-400">
{{ formatDate(article.publishedAt) }}
</time>
<span class="text-sm text-blue-400">{{ article.author.displayName }}</span>
<span class="text-sm text-blue-400">{{ article.author?.displayName ?? "System" }}</span>
</div>
<div class="mt-2">
<h3 class="text-xl font-semibold text-zinc-100 group-hover:text-primary-400">
{{ article.title }}
</h3>
<p class="mt-3 text-base text-zinc-400">
{{ article.excerpt }}
{{ article.description }}
</p>
</div>
</div>
@@ -70,7 +70,7 @@
</TransitionGroup>
<div
v-if="articles.length === 0"
v-if="articles?.length === 0"
class="text-center py-12"
>
<DocumentIcon class="mx-auto h-12 w-12 text-zinc-400" />
@@ -83,25 +83,11 @@
<script setup lang="ts">
import { DocumentIcon } from "@heroicons/vue/24/outline";
interface Article {
id: string;
title: string;
excerpt: string;
content: string;
tags: string[];
image: string | null;
publishedAt: string;
author: {
id: string;
displayName: string;
};
}
const newsDirectory = ref();
const { data: articles, refresh: refreshArticles } = await useNews().getAll();
const formatDate = (date: string) => {
return new Date(date).toLocaleDateString("en-US", {
return new Date(date).toLocaleDateString("en-AU", {
year: "numeric",
month: "long",
day: "numeric",

View File

@@ -1,16 +1,15 @@
import { defineEventHandler, createError } from "h3";
import aclManager from "~/server/internal/acls";
import newsManager from "~/server/internal/news";
export default defineEventHandler(async (event) => {
const userId = await event.context.session.getUserId(event);
if (!userId) {
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["news:delete"]);
if (!allowed)
throw createError({
statusCode: 401,
message: "Unauthorized",
statusCode: 403,
});
}
const id = event.context.params?.id;
const id = h3.context.params?.id;
if (!id) {
throw createError({
statusCode: 400,

View File

@@ -45,23 +45,27 @@ class NewsManager {
return await prisma.article.findMany({
where: {
AND: [
{
tags: {
some: { OR: options.tags?.map((e) => ({ name: e })) ?? [] },
},
},
{
title: {
search: options.search
},
description: {
search: options.search
},
content: {
search: options.search
}
}
],
options.tags
? {
tags: {
some: { OR: options.tags?.map((e) => ({ name: e })) ?? [] },
},
}
: undefined,
options.search
? {
title: {
search: options.search,
},
description: {
search: options.search,
},
content: {
search: options.search,
},
}
: undefined,
].filter((e) => e !== undefined),
},
take: options?.take || 10,
skip: options?.skip || 0,
@@ -75,6 +79,7 @@ class NewsManager {
displayName: true,
},
},
tags: true,
},
});
}
@@ -89,6 +94,7 @@ class NewsManager {
displayName: true,
},
},
tags: true,
},
});
}