mirror of
https://github.com/Drop-OSS/drop.git
synced 2026-01-31 15:37:09 +01:00
fix: news frontend
This commit is contained in:
@@ -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: []
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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'
|
||||
});
|
||||
};
|
||||
@@ -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) => {
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user