98 lines
2.7 KiB
TypeScript
98 lines
2.7 KiB
TypeScript
import { createSignal, For, createMemo } from "solid-js";
|
|
import { cache, createAsync, A, type RouteDefinition } from "@solidjs/router";
|
|
import { classList } from "solid-js/web";
|
|
|
|
const getNewsList = cache(async () => {
|
|
"use server";
|
|
return await fetch("https://content.dd-ix.net/news/en").then((data) =>
|
|
data.json()
|
|
);
|
|
}, "news-list");
|
|
|
|
const getNewsTags = cache(async () => {
|
|
"use server";
|
|
return await fetch("https://content.dd-ix.net/news/keywords").then((data) =>
|
|
data.json()
|
|
);
|
|
}, "news-tags");
|
|
|
|
export const route = {
|
|
load: () => {
|
|
getNewsList();
|
|
getNewsTags();
|
|
},
|
|
} satisfies RouteDefinition;
|
|
|
|
export default () => {
|
|
const newsList = createAsync(() => getNewsList());
|
|
const tagsData = createAsync(() => getNewsTags());
|
|
const [tags, setTags] = createSignal(
|
|
Object.fromEntries(
|
|
tagsData()?.map((tag: string) => [tag, { active: false }]) ?? []
|
|
)
|
|
);
|
|
|
|
const tagsActive = createMemo(() =>
|
|
Object.entries(tags())
|
|
.filter(([, { active }]) => active)
|
|
.map(([tag]) => tag)
|
|
);
|
|
|
|
return (
|
|
<main class="container mx-auto p-4">
|
|
<h1 class="mb-12 mt-40">News</h1>
|
|
<div class="flex flex-wrap gap-2">
|
|
<For each={Object.entries(tags())}>
|
|
{([tag, { active }]) => (
|
|
<button
|
|
class="btn btn-sm"
|
|
classList={{ "btn-primary": active }}
|
|
onClick={() =>
|
|
setTags({
|
|
...tags(),
|
|
[tag]: { active: !active },
|
|
})
|
|
}
|
|
>
|
|
{tag}
|
|
</button>
|
|
)}
|
|
</For>
|
|
</div>
|
|
<For
|
|
each={
|
|
tagsActive()?.length
|
|
? newsList()?.filter((news) =>
|
|
Object.entries(tags())
|
|
.filter(([, state]) => state.active)
|
|
.some(([tag]) => news.keywords.includes(tag))
|
|
)
|
|
: newsList()
|
|
}
|
|
>
|
|
{(post) => (
|
|
<A href={`/news/${post.slug}`}>
|
|
<figure class="card md:card-side bg-base-100 shadow-xl mt-4">
|
|
<img
|
|
src={`https://content.dd-ix.net/news/assets/${post.image}`}
|
|
alt={post.title}
|
|
class="md:w-48 object-cover"
|
|
/>
|
|
<div class="card-body">
|
|
<h2 class="card-title">{post.title}</h2>
|
|
<p>{post.published}</p>
|
|
<p>{post.description}</p>
|
|
<div class="flex gap-2">
|
|
<For each={post.keywords}>
|
|
{(tag) => <div class="badge badge-secondary">{tag}</div>}
|
|
</For>
|
|
</div>
|
|
</div>
|
|
</figure>
|
|
</A>
|
|
)}
|
|
</For>
|
|
</main>
|
|
);
|
|
};
|