♻️ Use keystonejs
This commit is contained in:
@@ -7,7 +7,7 @@ interface MenuItem {
|
||||
|
||||
const items: MenuItem[] = [
|
||||
{ href: "/posts", label: "记录" },
|
||||
{ href: "/events", label: "情报" },
|
||||
{ href: "/events", label: "活动" },
|
||||
{ href: "/projects", label: "企划" },
|
||||
];
|
||||
---
|
||||
|
@@ -13,11 +13,11 @@ const { posts } = Astro.props;
|
||||
posts?.map((item) => (
|
||||
<a href={`/posts/${item.slug}`}>
|
||||
<div class="card sm:card-side hover:bg-base-200 transition-colors sm:max-w-none shadow-xl">
|
||||
{item.heroImg && (
|
||||
{item.cover.image.url && (
|
||||
<figure class="mx-auto w-full object-cover p-6 max-sm:pb-0 sm:max-w-[12rem] sm:pe-0">
|
||||
<img
|
||||
loading="lazy"
|
||||
src={item.heroImg}
|
||||
src={item.cover.image.url}
|
||||
class="border-base-content bg-base-300 rounded-btn border border-opacity-5"
|
||||
alt={item.title}
|
||||
/>
|
||||
@@ -25,13 +25,13 @@ const { posts } = Astro.props;
|
||||
)}
|
||||
<div class="card-body">
|
||||
<h2 class="text-xl">{item.title}</h2>
|
||||
<div>
|
||||
<div class="mx-[-2px] mt-[-4px]">
|
||||
<span class="badge badge-accent">{POST_TYPES[item.type]}</span>
|
||||
{item.categories?.map((category: string) => (
|
||||
<span class="badge badge-primary">{category}</span>
|
||||
{item.categories?.map((category: any) => (
|
||||
<span class="badge badge-primary">{category.name}</span>
|
||||
))}
|
||||
{item.tags?.map((tag: string) => (
|
||||
<span class="badge badge-secondary">{tag}</span>
|
||||
{item.tags?.map((tag: any) => (
|
||||
<span class="badge badge-secondary">{tag.name}</span>
|
||||
))}
|
||||
</div>
|
||||
<div class="text-xs opacity-60 line-clamp-3">
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import { useState, Fragment } from "react";
|
||||
|
||||
export default function Video({
|
||||
export default function Media({
|
||||
sources,
|
||||
}: {
|
||||
sources: { caption: string; url: string }[];
|
||||
sources: { caption: string; url: string; type: string }[];
|
||||
}) {
|
||||
const [focus, setFocus] = useState<boolean[]>(
|
||||
sources.map((_, idx) => idx === 0)
|
||||
@@ -30,9 +30,16 @@ export default function Video({
|
||||
role="tabpanel"
|
||||
className="tab-content bg-base-100 border-base-300 rounded-box"
|
||||
>
|
||||
<video className="mb-0 block w-full h-[360px]" controls>
|
||||
{item.type === "video" && (
|
||||
<video className="mb-0 block w-full h-[360px]" controls>
|
||||
<source src={item.url} />
|
||||
</video>
|
||||
</video>
|
||||
)}
|
||||
{item.type === "audio" && (
|
||||
<audio className="mb-0 block w-full h-[20px]" controls>
|
||||
<source src={item.url} />
|
||||
</audio>
|
||||
)}
|
||||
</div>
|
||||
</Fragment>
|
||||
))}
|
@@ -1,33 +1,48 @@
|
||||
---
|
||||
import PageLayout from "../../layouts/PageLayout.astro";
|
||||
|
||||
import { client } from "../../../tina/__generated__/client";
|
||||
import PostList from "../../components/PostList.astro";
|
||||
|
||||
import { graphQuery } from "../../scripts/requests";
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
const { slug } = Astro.params;
|
||||
|
||||
const postsResponse = await client.queries.postConnection({
|
||||
filter: { categories: { in: [slug ?? "index"] } },
|
||||
});
|
||||
const posts = postsResponse.data.postConnection.edges
|
||||
?.sort((a, b) =>
|
||||
new Date(a?.node?.date ?? 0).getTime() <=
|
||||
new Date(b?.node?.date ?? 0).getTime()
|
||||
? -1
|
||||
: 0
|
||||
const { posts } = (
|
||||
await graphQuery(
|
||||
`query Query($where: PostWhereInput!) {
|
||||
posts(where: $where) {
|
||||
slug
|
||||
type
|
||||
title
|
||||
description
|
||||
cover {
|
||||
image {
|
||||
url
|
||||
}
|
||||
}
|
||||
content {
|
||||
document
|
||||
}
|
||||
categories {
|
||||
name
|
||||
}
|
||||
tags {
|
||||
name
|
||||
}
|
||||
createdAt
|
||||
}
|
||||
}`,
|
||||
{ where: { categories: { some: { slug: { equals: slug } } } } }
|
||||
)
|
||||
.map((event) => {
|
||||
return { ...event?.node, slug: event?.node?._sys.filename };
|
||||
});
|
||||
).data;
|
||||
---
|
||||
|
||||
<PageLayout>
|
||||
<div class="max-w-[720px] mx-auto">
|
||||
<div class="pt-16 pb-6 px-6">
|
||||
<h1 class="text-4xl font-bold">分类检索</h1>
|
||||
<p class="pt-3">以下是包含「{slug}」分类的记录……</p>
|
||||
<p class="pt-3">以下是包含该分类的记录……</p>
|
||||
</div>
|
||||
|
||||
<PostList posts={posts as any[]} />
|
||||
|
@@ -1,29 +1,40 @@
|
||||
---
|
||||
import PageLayout from "../../layouts/PageLayout.astro";
|
||||
|
||||
import { client } from "../../../tina/__generated__/client";
|
||||
import { TinaMarkdown } from "tinacms/dist/rich-text";
|
||||
import { graphQuery } from "../../scripts/requests";
|
||||
import { DocumentRenderer } from "@keystone-6/document-renderer";
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
const eventsResponse = await client.queries.eventConnection();
|
||||
const events = eventsResponse.data.eventConnection.edges
|
||||
?.sort((a, b) =>
|
||||
new Date(a?.node?.date ?? 0).getTime() <=
|
||||
new Date(b?.node?.date ?? 0).getTime()
|
||||
? -1
|
||||
: 0
|
||||
const { events } = (
|
||||
await graphQuery(
|
||||
`query Query($where: EventWhereInput!) {
|
||||
events(where: $where) {
|
||||
slug
|
||||
title
|
||||
description
|
||||
content {
|
||||
document
|
||||
}
|
||||
createdAt
|
||||
}
|
||||
}`,
|
||||
{
|
||||
where: {
|
||||
isHistory: {
|
||||
equals: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
.map((event) => {
|
||||
return { ...event?.node, slug: event?.node?._sys.filename };
|
||||
});
|
||||
).data;
|
||||
---
|
||||
|
||||
<PageLayout>
|
||||
<div class="max-w-[720px] mx-auto">
|
||||
<div class="card w-full shadow-xl">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">情报</h2>
|
||||
<h2 class="card-title">活动</h2>
|
||||
<p>读岁月史书,涨人生阅历</p>
|
||||
<div class="divider"></div>
|
||||
|
||||
@@ -32,8 +43,8 @@ const events = eventsResponse.data.eventConnection.edges
|
||||
>
|
||||
{
|
||||
events?.map((item: any, idx: number) => {
|
||||
let align = idx % 2 === 0 ? "start" : "end";
|
||||
let textAlign = idx % 2 === 0 ? "left" : "right";
|
||||
let align = idx % 2 === 0 ? "timeline-start" : "timeline-end";
|
||||
let textAlign = idx % 2 === 0 ? "md:text-right" : "md:text-left";
|
||||
|
||||
return (
|
||||
<li>
|
||||
@@ -52,12 +63,12 @@ const events = eventsResponse.data.eventConnection.edges
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class={`timeline-${align} md:text-${textAlign} mb-10`}>
|
||||
<div class={`${align} ${textAlign} mb-10`}>
|
||||
<time class="font-mono italic">
|
||||
{new Date(item.date).toLocaleDateString()}
|
||||
{new Date(item.createdAt).toLocaleDateString()}
|
||||
</time>
|
||||
<div class="text-lg font-black">{item.title}</div>
|
||||
<TinaMarkdown content={item._body} />
|
||||
<DocumentRenderer document={item.content.document} />
|
||||
</div>
|
||||
<hr />
|
||||
</li>
|
||||
@@ -65,8 +76,10 @@ const events = eventsResponse.data.eventConnection.edges
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
<div class="text-center max-md:text-left italic">我们的故事还在继续……</div>
|
||||
<div class="text-center max-md:text-left italic">
|
||||
我们的故事还在继续……
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PageLayout>
|
||||
</PageLayout>
|
@@ -1,19 +1,27 @@
|
||||
---
|
||||
import RootLayout from "../layouts/RootLayout.astro";
|
||||
|
||||
import { client } from "../../tina/__generated__/client";
|
||||
import { graphQuery } from "../scripts/requests";
|
||||
|
||||
const eventsResponse = await client.queries.eventConnection();
|
||||
const events = eventsResponse.data.eventConnection.edges
|
||||
?.sort((a, b) =>
|
||||
new Date(a?.node?.date ?? 0).getTime() <=
|
||||
new Date(b?.node?.date ?? 0).getTime()
|
||||
? -1
|
||||
: 0
|
||||
const { events } = (
|
||||
await graphQuery(
|
||||
`query Query($where: EventWhereInput!) {
|
||||
events(where: $where) {
|
||||
slug
|
||||
title
|
||||
description
|
||||
createdAt
|
||||
}
|
||||
}`,
|
||||
{
|
||||
where: {
|
||||
isHistory: {
|
||||
equals: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
.map((event) => {
|
||||
return { ...event?.node, slug: event?.node?._sys.filename };
|
||||
});
|
||||
).data;
|
||||
---
|
||||
|
||||
<RootLayout>
|
||||
@@ -140,7 +148,7 @@ const events = eventsResponse.data.eventConnection.edges
|
||||
<li>
|
||||
{idx > 0 && <hr />}
|
||||
<div class="timeline-start">
|
||||
{new Date(item.date).toLocaleDateString()}
|
||||
{new Date(item.createdAt).toLocaleDateString()}
|
||||
</div>
|
||||
<div class="timeline-middle">
|
||||
<svg
|
||||
|
@@ -1,41 +1,78 @@
|
||||
---
|
||||
import PageLayout from "../../layouts/PageLayout.astro";
|
||||
import Video from "../../components/posts/Video.tsx";
|
||||
// @ts-ignore
|
||||
import Media from "../../components/posts/Media";
|
||||
|
||||
import { POST_TYPES } from "../../scripts/consts";
|
||||
import { client } from "../../../tina/__generated__/client";
|
||||
import { TinaMarkdown } from "tinacms/dist/rich-text";
|
||||
import { graphQuery } from "../../scripts/requests";
|
||||
import { DocumentRenderer } from "@keystone-6/document-renderer";
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
const { slug } = Astro.params;
|
||||
|
||||
const components = {
|
||||
Video,
|
||||
}
|
||||
|
||||
const { data } = await client.queries.post({
|
||||
relativePath: (slug ?? "index") + ".mdx",
|
||||
});
|
||||
const { post } = (
|
||||
await graphQuery(
|
||||
`query Query($where: PostWhereUniqueInput!) {
|
||||
post(where: $where) {
|
||||
slug
|
||||
type
|
||||
title
|
||||
description
|
||||
assets {
|
||||
caption
|
||||
url
|
||||
type
|
||||
}
|
||||
cover {
|
||||
image {
|
||||
url
|
||||
}
|
||||
}
|
||||
content {
|
||||
document
|
||||
}
|
||||
categories {
|
||||
slug
|
||||
name
|
||||
}
|
||||
tags {
|
||||
slug
|
||||
name
|
||||
}
|
||||
createdAt
|
||||
}
|
||||
}`,
|
||||
{ where: { slug } }
|
||||
)
|
||||
).data;
|
||||
---
|
||||
|
||||
<PageLayout>
|
||||
<div class="wrapper">
|
||||
<div class="card w-full shadow-xl">
|
||||
{
|
||||
data.post.heroImg && (
|
||||
post.cover != null && (
|
||||
<figure>
|
||||
<img src={data.post.heroImg} alt={data.post.title} />
|
||||
<img src={post.cover.image.url} alt={post.title} />
|
||||
</figure>
|
||||
)
|
||||
}
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">{data.post.title}</h2>
|
||||
<p class="description">{data.post.description ?? "No description"}</p>
|
||||
<h2 class="card-title">{post.title}</h2>
|
||||
<p class="description">{post.description ?? "No description"}</p>
|
||||
<div class="divider"></div>
|
||||
|
||||
{
|
||||
post.assets?.length > 0 && (
|
||||
<div class="mb-5">
|
||||
<Media sources={post.assets} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<div class="prose max-w-none">
|
||||
<TinaMarkdown content={data.post._body} components={components} />
|
||||
<DocumentRenderer document={post.content.document} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -46,19 +83,24 @@ const { data } = await client.queries.post({
|
||||
<div class="gap-2 text-sm metadata description">
|
||||
<div>
|
||||
<div>作者</div>
|
||||
<div>{data.post.author?.name ?? "佚名"}</div>
|
||||
<div>{post.author?.name ?? "佚名"}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>类型</div>
|
||||
<div class="text-accent">{POST_TYPES[data.post.type as unknown as string]}</div>
|
||||
<div class="text-accent">
|
||||
{POST_TYPES[post.type as unknown as string]}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>分类</div>
|
||||
<div class="flex gap-1">
|
||||
{
|
||||
data.post.categories?.map((category) => (
|
||||
<a href={`/categories/${category}`} class="link link-primary">
|
||||
{category}
|
||||
post.categories?.map((category: any) => (
|
||||
<a
|
||||
href={`/categories/${category.slug}`}
|
||||
class="link link-primary"
|
||||
>
|
||||
{category.name}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
@@ -68,9 +110,9 @@ const { data } = await client.queries.post({
|
||||
<div>标签</div>
|
||||
<div class="flex gap-1">
|
||||
{
|
||||
data.post.tags?.map((tag) => (
|
||||
<a href={`/tags/${tag}`} class="link link-secondary">
|
||||
{tag}
|
||||
post.tags?.map((tag: any) => (
|
||||
<a href={`/tags/${tag.slug}`} class="link link-secondary">
|
||||
{tag.name}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
@@ -78,7 +120,7 @@ const { data } = await client.queries.post({
|
||||
</div>
|
||||
<div>
|
||||
<div>发布于</div>
|
||||
<div>{new Date(data.post.date ?? 0).toLocaleString()}</div>
|
||||
<div>{new Date(post.createdAt).toLocaleString()}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,30 +1,48 @@
|
||||
---
|
||||
import PageLayout from "../../layouts/PageLayout.astro";
|
||||
|
||||
import { client } from "../../../tina/__generated__/client";
|
||||
import PostList from "../../components/PostList.astro";
|
||||
|
||||
import { graphQuery } from "../../scripts/requests";
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
const postsResponse = await client.queries.postConnection();
|
||||
const posts = postsResponse.data.postConnection.edges
|
||||
?.sort((a, b) =>
|
||||
new Date(a?.node?.date ?? 0).getTime() <=
|
||||
new Date(b?.node?.date ?? 0).getTime()
|
||||
? -1
|
||||
: 0
|
||||
const { posts } = (
|
||||
await graphQuery(
|
||||
`query Query($where: PostWhereInput!) {
|
||||
posts(where: $where) {
|
||||
slug
|
||||
type
|
||||
title
|
||||
description
|
||||
cover {
|
||||
image {
|
||||
url
|
||||
}
|
||||
}
|
||||
content {
|
||||
document
|
||||
}
|
||||
categories {
|
||||
name
|
||||
}
|
||||
tags {
|
||||
name
|
||||
}
|
||||
createdAt
|
||||
}
|
||||
}`,
|
||||
{ where: {} }
|
||||
)
|
||||
.map((event) => {
|
||||
return { ...event?.node, slug: event?.node?._sys.filename };
|
||||
});
|
||||
).data;
|
||||
---
|
||||
|
||||
<PageLayout>
|
||||
<div class="max-w-[720px] mx-auto">
|
||||
<div class="pt-16 pb-6 px-6">
|
||||
<h1 class="text-4xl font-bold">记录</h1>
|
||||
<p class="pt-3">记录生活,记录理想,记录记录……</p>
|
||||
<p class="pt-2">记录生活,记录理想,记录记录……</p>
|
||||
</div>
|
||||
|
||||
<PostList posts={posts as any[]} />
|
||||
</div>
|
||||
</PageLayout>
|
||||
|
@@ -1,33 +1,48 @@
|
||||
---
|
||||
import PageLayout from "../../layouts/PageLayout.astro";
|
||||
|
||||
import { client } from "../../../tina/__generated__/client";
|
||||
import PostList from "../../components/PostList.astro";
|
||||
|
||||
import { graphQuery } from "../../scripts/requests";
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
const { slug } = Astro.params;
|
||||
|
||||
const postsResponse = await client.queries.postConnection({
|
||||
filter: { tags: { in: [slug ?? "index"] } },
|
||||
});
|
||||
const posts = postsResponse.data.postConnection.edges
|
||||
?.sort((a, b) =>
|
||||
new Date(a?.node?.date ?? 0).getTime() <=
|
||||
new Date(b?.node?.date ?? 0).getTime()
|
||||
? -1
|
||||
: 0
|
||||
const { posts } = (
|
||||
await graphQuery(
|
||||
`query Query($where: PostWhereInput!) {
|
||||
posts(where: $where) {
|
||||
slug
|
||||
type
|
||||
title
|
||||
description
|
||||
cover {
|
||||
image {
|
||||
url
|
||||
}
|
||||
}
|
||||
content {
|
||||
document
|
||||
}
|
||||
categories {
|
||||
name
|
||||
}
|
||||
tags {
|
||||
name
|
||||
}
|
||||
createdAt
|
||||
}
|
||||
}`,
|
||||
{ where: { tags: { some: { slug: { equals: slug } } } } }
|
||||
)
|
||||
.map((event) => {
|
||||
return { ...event?.node, slug: event?.node?._sys.filename };
|
||||
});
|
||||
).data;
|
||||
---
|
||||
|
||||
<PageLayout>
|
||||
<div class="max-w-[720px] mx-auto">
|
||||
<div class="pt-16 pb-6 px-6">
|
||||
<h1 class="text-4xl font-bold">标签检索</h1>
|
||||
<p class="pt-3">以下是包含「{slug}」标签的记录……</p>
|
||||
<p class="pt-3">以下是包含该标签的记录……</p>
|
||||
</div>
|
||||
|
||||
<PostList posts={posts as any[]} />
|
||||
|
15
src/scripts/requests.ts
Normal file
15
src/scripts/requests.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export async function graphQuery(query: string, variables: any) {
|
||||
const response = await fetch(
|
||||
`${import.meta.env.PUBLIC_CMS}/api/graphql`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
query,
|
||||
variables,
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
return await response.json();
|
||||
}
|
Reference in New Issue
Block a user