This commit is contained in:
parent
729704984c
commit
4bd975796e
148 changed files with 4865 additions and 27561 deletions
20
src/pages/[year].astro
Normal file
20
src/pages/[year].astro
Normal file
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
import type { GetStaticPaths } from "astro";
|
||||
import { getCollection } from "astro:content";
|
||||
import { shouldBeVisible } from "~/utils/posts";
|
||||
|
||||
export const getStaticPaths = (async () => {
|
||||
const posts = (await getCollection("posts")).filter(shouldBeVisible);
|
||||
const years = new Set(
|
||||
posts.map((x) => x.data.date.getUTCFullYear().toString())
|
||||
);
|
||||
return years
|
||||
.values()
|
||||
.map((y) => ({ params: { year: y }, props: { year: y } }))
|
||||
.toArray();
|
||||
}) satisfies GetStaticPaths;
|
||||
|
||||
const { year } = Astro.props;
|
||||
|
||||
return Astro.rewrite(`/${year}/page/1`);
|
||||
---
|
109
src/pages/[year]/[month]/[date]/[postid].astro
Normal file
109
src/pages/[year]/[month]/[date]/[postid].astro
Normal file
|
@ -0,0 +1,109 @@
|
|||
---
|
||||
import "~/material/content.css";
|
||||
import type { GetStaticPaths } from "astro";
|
||||
import { render } from "astro:content";
|
||||
import { getCollection } from "astro:content";
|
||||
import { format } from "date-fns";
|
||||
import TimeDistanceToNow from "~/components/TimeDistanceToNow/index.astro";
|
||||
import Regular from "~/layouts/Regular.astro";
|
||||
import { getPostParam } from "~/utils/posts";
|
||||
|
||||
export const getStaticPaths = (async () => {
|
||||
const posts = await getCollection("posts");
|
||||
|
||||
return posts.map((x) => ({
|
||||
params: getPostParam(x),
|
||||
props: {
|
||||
post: x,
|
||||
},
|
||||
}));
|
||||
}) satisfies GetStaticPaths;
|
||||
|
||||
const { post } = Astro.props;
|
||||
|
||||
const { Content } = await render(post);
|
||||
---
|
||||
|
||||
<Regular title={post.data.title}>
|
||||
<div id="_layout">
|
||||
<div></div>
|
||||
<main class="content">
|
||||
<h1 transition:name={`post:${post.id}:title`}>{post.data.title}</h1>
|
||||
<div class="page-metadata">
|
||||
<span>
|
||||
<TimeDistanceToNow datetime={post.data.date.toISOString()}
|
||||
>{format(post.data.date, "yyyy/MM/dd")}</TimeDistanceToNow
|
||||
>
|
||||
创建
|
||||
</span>
|
||||
{
|
||||
post.data.updated && (
|
||||
<span>
|
||||
<TimeDistanceToNow datetime={post.data.updated.toISOString()}>
|
||||
{format(post.data.updated, "yyyy/MM/dd")}
|
||||
</TimeDistanceToNow>
|
||||
更新
|
||||
</span>
|
||||
)
|
||||
}
|
||||
{post.data.visibility === "draft" && <span>This is a draft</span>}
|
||||
</div>
|
||||
<Content />
|
||||
</main>
|
||||
<div></div>
|
||||
</div>
|
||||
</Regular>
|
||||
|
||||
<style>
|
||||
:global(:root) {
|
||||
background-color: var(--palette-grey-200);
|
||||
}
|
||||
|
||||
#_layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
margin: auto;
|
||||
|
||||
& > :global(*) {
|
||||
overflow: hidden;
|
||||
word-wrap: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.page-metadata {
|
||||
display: grid;
|
||||
justify-content: flex-end;
|
||||
margin-inline: 16px;
|
||||
gap: 4px;
|
||||
color: var(--palette-grey-700);
|
||||
|
||||
> * {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 70rem;
|
||||
margin-top: 32px;
|
||||
margin-bottom: calc(env(safe-area-insets-bottom, 16px) + 16px);
|
||||
background-color: var(--palette-grey-50);
|
||||
padding-block: 16px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { wrapElementsInClass } from "~/utils/dom";
|
||||
import { renderAdvancedTablesOn } from "~/utils/table";
|
||||
|
||||
document.addEventListener("astro:page-load", () => {
|
||||
wrapElementsInClass(document.querySelectorAll(".content > table"), [
|
||||
"table-responsive",
|
||||
]);
|
||||
renderAdvancedTablesOn(
|
||||
document.querySelectorAll(".content > .table-responsive > table")
|
||||
);
|
||||
});
|
||||
</script>
|
68
src/pages/[year]/page/[page].astro
Normal file
68
src/pages/[year]/page/[page].astro
Normal file
|
@ -0,0 +1,68 @@
|
|||
---
|
||||
import type { GetStaticPaths } from "astro";
|
||||
import type { CollectionEntry } from "astro:content";
|
||||
import { getCollection } from "astro:content";
|
||||
import AuthorCard from "~/components/AuthorCard.astro";
|
||||
import Pager from "~/components/Pager.astro";
|
||||
import PostList from "~/components/PostList.astro";
|
||||
import TagListCard from "~/components/TagListCard.astro";
|
||||
import IndexLayout from "~/layouts/IndexLayout.astro";
|
||||
import { getAllTags, getHotTags, shouldBeVisible } from "~/utils/posts";
|
||||
|
||||
export const getStaticPaths = (async ({ paginate }) => {
|
||||
const posts = (await getCollection("posts"))
|
||||
.filter(shouldBeVisible)
|
||||
.sort((a, b) => b.data.date.getTime() - a.data.date.getTime());
|
||||
const groupByYear = new Map<string, CollectionEntry<"posts">[]>();
|
||||
|
||||
for (const post of posts) {
|
||||
const year = post.data.date.getUTCFullYear().toString();
|
||||
|
||||
const collection = groupByYear.get(year);
|
||||
if (collection) {
|
||||
collection.push(post);
|
||||
} else {
|
||||
groupByYear.set(year, [post]);
|
||||
}
|
||||
}
|
||||
|
||||
return groupByYear
|
||||
.entries()
|
||||
.flatMap(([year, postsOfYear]) => {
|
||||
return paginate(postsOfYear, {
|
||||
params: {
|
||||
year,
|
||||
},
|
||||
props: {
|
||||
year,
|
||||
},
|
||||
});
|
||||
})
|
||||
.toArray();
|
||||
}) satisfies GetStaticPaths;
|
||||
|
||||
const { year, page } = Astro.props;
|
||||
|
||||
const authors = (await getCollection("authors")).filter((a) => a.data.default);
|
||||
|
||||
const posts = (await getCollection("posts")).filter(
|
||||
(p) => p.data.date.getUTCFullYear().toString() === year
|
||||
);
|
||||
const tags = getAllTags(posts);
|
||||
const hotTags = getHotTags(posts, tags);
|
||||
---
|
||||
|
||||
<IndexLayout title={`${year}年`}>
|
||||
<main>
|
||||
<PostList posts={page.data} />
|
||||
<div style="display: flex; justify-content: center; margin: 16px;">
|
||||
<Pager {...page} urlForPage={(n) => `/page/${n}`} />
|
||||
</div>
|
||||
</main>
|
||||
<div
|
||||
style="display: flex; flex-flow: column nowrap; gap: 16px; height: fit-content; position: sticky; top: 0;"
|
||||
>
|
||||
{authors.map((item) => <AuthorCard {...item.data} />)}
|
||||
<TagListCard title={`${year}年的标签`} tags={tags} hotTags={hotTags} />
|
||||
</div>
|
||||
</IndexLayout>
|
58
src/pages/_headers.ts
Normal file
58
src/pages/_headers.ts
Normal file
|
@ -0,0 +1,58 @@
|
|||
import type { APIRoute } from "astro";
|
||||
import { getCollection } from "astro:content";
|
||||
import { postParamlink } from "~/utils/posts";
|
||||
|
||||
export const GET: APIRoute = async () => {
|
||||
const headers = new Map<string, Headers>();
|
||||
|
||||
headers.set(
|
||||
"/*",
|
||||
new Headers({
|
||||
"Cache-Control": "public, max-age=1440, must-revalidate",
|
||||
}),
|
||||
);
|
||||
|
||||
headers.set(
|
||||
"/_astro/*",
|
||||
new Headers({
|
||||
"Cache-Control": "public, max-age=31536000, immutable",
|
||||
}),
|
||||
);
|
||||
|
||||
const posts = await getCollection("posts");
|
||||
|
||||
for (const post of posts) {
|
||||
const etagContent = JSON.stringify({
|
||||
id: post.id,
|
||||
body: post.body,
|
||||
data: post.data,
|
||||
});
|
||||
const etag = `"${new Bun.SHA256().update(etagContent).digest("hex")}"`;
|
||||
|
||||
const matcher = postParamlink(post);
|
||||
headers.set(
|
||||
matcher,
|
||||
new Headers({
|
||||
ETag: etag,
|
||||
}),
|
||||
);
|
||||
|
||||
headers.set(
|
||||
`${matcher}index.html`,
|
||||
new Headers({
|
||||
ETag: etag,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const lines = [] as string[];
|
||||
|
||||
for (const [k, entries] of headers) {
|
||||
lines.push(k);
|
||||
for (const [name, value] of entries) {
|
||||
lines.push(` ${name}: ${value}`);
|
||||
}
|
||||
}
|
||||
|
||||
return new Response(lines.join("\n"));
|
||||
};
|
82
src/pages/archives.astro
Normal file
82
src/pages/archives.astro
Normal file
|
@ -0,0 +1,82 @@
|
|||
---
|
||||
import { getCollection } from "astro:content";
|
||||
import Regular from "~/layouts/Regular.astro";
|
||||
import { shouldBeVisible } from "~/utils/posts";
|
||||
|
||||
const posts = (await getCollection("posts")).filter(shouldBeVisible);
|
||||
|
||||
const postCountByYear = new Map<string, number>();
|
||||
|
||||
for (const post of posts) {
|
||||
const year = post.data.date.getUTCFullYear().toString();
|
||||
|
||||
const ocount = postCountByYear.get(year) ?? 0;
|
||||
|
||||
postCountByYear.set(year, ocount + 1);
|
||||
}
|
||||
|
||||
const counts = postCountByYear
|
||||
.entries()
|
||||
.toArray()
|
||||
.sort(([y0], [y1]) => Number(y1) - Number(y0));
|
||||
---
|
||||
|
||||
<Regular title="所有归档">
|
||||
<main id="_layout">
|
||||
<ul class="archive-list">
|
||||
{
|
||||
counts.map(([year, count]) => {
|
||||
return (
|
||||
<li class="archive-list-item">
|
||||
<a class="archive-list-link" href={`/${year}/`}>
|
||||
{year}
|
||||
</a>
|
||||
<span class="archive-list-count">{count}</span>
|
||||
</li>
|
||||
);
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</main>
|
||||
</Regular>
|
||||
|
||||
<style>
|
||||
#_layout {
|
||||
display: grid;
|
||||
margin-inline: 60px;
|
||||
padding-block: 24px;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
#_layout {
|
||||
margin-inline: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
#_layout > :first-child {
|
||||
max-width: 560px;
|
||||
}
|
||||
|
||||
.archive-list {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
gap: 12px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.archive-list-item {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid var(--palette-grey-500);
|
||||
}
|
||||
|
||||
a.archive-list-link {
|
||||
font: var(--typ-title);
|
||||
line-height: 2;
|
||||
padding-right: 48px;
|
||||
padding-left: 16px;
|
||||
text-align: start;
|
||||
}
|
||||
</style>
|
99
src/pages/atom.xml.ts
Normal file
99
src/pages/atom.xml.ts
Normal file
|
@ -0,0 +1,99 @@
|
|||
import type { APIRoute } from "astro";
|
||||
import { experimental_AstroContainer as AstroContainer } from "astro/container";
|
||||
import { loadRenderers } from "astro:container";
|
||||
import { getContainerRenderer as solidContainerRenderer } from "@astrojs/solid-js";
|
||||
import { getContainerRenderer as mdxContainerRenderer } from "@astrojs/mdx";
|
||||
import { getCollection, render } from "astro:content";
|
||||
import { x } from "xastscript";
|
||||
import { postParamlink, shouldBeVisible } from "~/utils/posts";
|
||||
import { toXml } from "xast-util-to-xml";
|
||||
|
||||
export const GET: APIRoute = async () => {
|
||||
const renderers = await loadRenderers([
|
||||
solidContainerRenderer(),
|
||||
mdxContainerRenderer(),
|
||||
]);
|
||||
const cont = await AstroContainer.create({
|
||||
renderers: renderers,
|
||||
});
|
||||
const posts = (await getCollection("posts"))
|
||||
.filter(shouldBeVisible)
|
||||
.sort((a, b) => b.data.date.getTime() - a.data.date.getTime())
|
||||
.slice(0, 10);
|
||||
const authors = await getCollection("authors");
|
||||
|
||||
const tr = x(
|
||||
"feed",
|
||||
{ xmlns: "http://www.w3.org/2005/Atom" },
|
||||
x("title", { type: "text" }, "Rubicon's Rubicon"),
|
||||
x("id", {}, "https://rubicon.lightstands.xyz/"),
|
||||
x(
|
||||
"updated",
|
||||
{},
|
||||
posts.length > 0
|
||||
? posts[0].data.date.toISOString()
|
||||
: new Date().toISOString(),
|
||||
),
|
||||
x("link", {
|
||||
rel: "alternate",
|
||||
type: "text/html",
|
||||
hreflang: "zh",
|
||||
href: "https://rubicon.lightstands.xyz",
|
||||
}),
|
||||
x("link", {
|
||||
rel: "alternate",
|
||||
type: "application/feed+json",
|
||||
hreflang: "zh",
|
||||
href: "https://rubicon.lightstands.xyz/feed.json",
|
||||
}),
|
||||
x("link", {
|
||||
rel: "self",
|
||||
type: "application/atom+xml",
|
||||
href: "https://rubicon.lightstands.xyz/atom.xml",
|
||||
}),
|
||||
...(await Promise.all(
|
||||
posts.map(async (item) => {
|
||||
const href = new URL(
|
||||
postParamlink(item),
|
||||
"https://rubicon.lightstands.xyz",
|
||||
).toString();
|
||||
const { Content } = await render(item);
|
||||
return x(
|
||||
"entry",
|
||||
{},
|
||||
x("title", {}, item.data.title),
|
||||
x("id", {}, href),
|
||||
x("published", {}, item.data.date.toISOString()),
|
||||
x(
|
||||
"updated",
|
||||
{},
|
||||
item.data.updated
|
||||
? item.data.updated.toISOString()
|
||||
: item.data.date.toISOString(),
|
||||
),
|
||||
x("link", {
|
||||
rel: "alternate",
|
||||
type: "application/xhtml+html",
|
||||
href,
|
||||
}),
|
||||
...authors.map((author) => {
|
||||
return x(
|
||||
"author",
|
||||
{},
|
||||
x("name", {}, author.data.name),
|
||||
(author.data.links?.length ?? 0 > 0)
|
||||
? x("uri", {}, author.data.links![0].href)
|
||||
: undefined,
|
||||
);
|
||||
}),
|
||||
x(
|
||||
"content",
|
||||
{ type: "text/html", "xml:base": href },
|
||||
await cont.renderToString(Content),
|
||||
),
|
||||
);
|
||||
}),
|
||||
)),
|
||||
);
|
||||
return new Response(toXml(tr, { allowDangerousXml: true }));
|
||||
};
|
146
src/pages/external-resource-usage.astro
Normal file
146
src/pages/external-resource-usage.astro
Normal file
|
@ -0,0 +1,146 @@
|
|||
---
|
||||
import "~/material/content.css";
|
||||
import { getCollection } from "astro:content";
|
||||
import AuthorCard from "~/components/AuthorCard.astro";
|
||||
import Wikipedia from "~/components/Wikipedia.astro";
|
||||
import Regular from "~/layouts/Regular.astro";
|
||||
|
||||
const defaultAuthors = (await getCollection("authors")).filter(
|
||||
(x) => x.data.default
|
||||
);
|
||||
---
|
||||
|
||||
<Regular title="External Resource Usage" lang="en">
|
||||
<main>
|
||||
<section>
|
||||
<h2>Contract Infomation</h2>
|
||||
<div class="cols-2">
|
||||
<div>
|
||||
<p>
|
||||
Please tell me if you found unaccpectable traffic coming from this
|
||||
website.
|
||||
</p>
|
||||
<p>
|
||||
Please check if the traffic actually coming from this site
|
||||
<a href="https://rubicon.lightstands.xyz">rubicon.lightstands.xyz</a
|
||||
>. The source code of this site is open source - anyone can use it
|
||||
and its user agent string to create abuse traffic.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{defaultAuthors.map((author) => <AuthorCard {...author.data} />)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Fetching from Wikipedia</h2>
|
||||
|
||||
<div class="cols-2" style="align-items: center;">
|
||||
<div class="content">
|
||||
<Wikipedia page="Wikipedia" />
|
||||
</div>
|
||||
|
||||
<p>
|
||||
This website uses Wikipedia's content to explain terms and ideas to
|
||||
the visitors, mostly in the form of blockquote, which automatically
|
||||
fetched by the software depending on the argument provided by the
|
||||
author.
|
||||
</p>
|
||||
</div>
|
||||
<p>
|
||||
To achive the best user experience, for every such blockquote, this
|
||||
software does 1 fetch as they appear in the post. Possibly additional 1
|
||||
fetch for every shown of the blockquote. Each fetch is assocatiated 1 to
|
||||
3 requests to variaous Wikipedia instance.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The first required fetch is the baking fetch, which appears on the
|
||||
machine building the software. This software, is being built at once for
|
||||
every published website change, bakes the requested content in the page.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<span style="font-weight: bold;">In the building process,</span> every requested
|
||||
blockquote will trigger 1 or 2 requests to various instance of Wikipedia.
|
||||
Due to technology limit, there is not implemented limitation for the advised
|
||||
200reqs/min. I think that's acceptable because the building is rare, usually
|
||||
less than 10 times each months, and in one-at-a-time basis, and each building
|
||||
unlikely create requests more than 5000 . As the written of this page, 2024/04/06,
|
||||
there just are less than 20 requests.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In this matter, the user agent string includes the text "on Server",
|
||||
indicates this fetch happens on server.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Worth noting that one post may be rendered multiple times, so the
|
||||
blockquotes technically may appear multiple times. This software
|
||||
provides a whole site JSON feed and a recent Atom feed in addition to
|
||||
the normal HTML pages. So each blockquote might be fetch 2 - 3 times for
|
||||
the baking.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<span style="font-weight: bold;">The requests happen on client</span> when
|
||||
the user agent string includes "on Client". They appear when the client-side
|
||||
script trys to enhance the experience by swap the content with the user accepted
|
||||
languages. The page content can remain untouched if this enhancement process
|
||||
failed.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Just like the baked fetch, those enhancement fetch is without
|
||||
implemented limitation for the advised 200reqs/min. We think that's
|
||||
highly unlikely to have 100 blockquotes from Wikipedia in a post.
|
||||
</p>
|
||||
</section>
|
||||
</main>
|
||||
</Regular>
|
||||
|
||||
<style>
|
||||
main {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 32px);
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 36px;
|
||||
margin-bottom: 24px;
|
||||
margin-inline: 8px;
|
||||
}
|
||||
|
||||
p {
|
||||
max-width: 120ch;
|
||||
margin-inline: 16px;
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
p + p {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.cols-2 {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
gap: 8px;
|
||||
|
||||
> * {
|
||||
max-width: 60ch;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
> :first-child {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
> :last-child {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
5
src/pages/feed.json.ts
Normal file
5
src/pages/feed.json.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import type { APIRoute } from "astro";
|
||||
|
||||
export const GET: APIRoute = ({rewrite}) => {
|
||||
return rewrite("/feeds/1.json")
|
||||
}
|
58
src/pages/feeds/[page].json.ts
Normal file
58
src/pages/feeds/[page].json.ts
Normal file
|
@ -0,0 +1,58 @@
|
|||
import type { APIRoute, GetStaticPaths, Page } from "astro";
|
||||
import { getCollection, render, type CollectionEntry } from "astro:content";
|
||||
import { experimental_AstroContainer as AstroContainer } from "astro/container";
|
||||
import { loadRenderers } from "astro:container";
|
||||
import { getContainerRenderer as solidContainerRenderer } from "@astrojs/solid-js";
|
||||
import { getContainerRenderer as mdxContainerRenderer } from "@astrojs/mdx";
|
||||
import { postParamlink, shouldBeVisible } from "~/utils/posts";
|
||||
|
||||
export const getStaticPaths = (async ({ paginate }) => {
|
||||
const posts = (await getCollection("posts")).filter(shouldBeVisible).sort(
|
||||
(a, b) => b.data.date.getTime() - a.data.date.getTime(),
|
||||
);
|
||||
|
||||
return paginate(posts, {pageSize: 5});
|
||||
}) satisfies GetStaticPaths;
|
||||
|
||||
export const GET: APIRoute<{ page: Page<CollectionEntry<"posts">> }> = async ({
|
||||
props: { page },
|
||||
}) => {
|
||||
const renderers = await loadRenderers([
|
||||
solidContainerRenderer(),
|
||||
mdxContainerRenderer(),
|
||||
]);
|
||||
const cont = await AstroContainer.create({
|
||||
renderers: renderers,
|
||||
});
|
||||
const authors = await getCollection("authors");
|
||||
|
||||
const hasNextPage = page.currentPage < page.start + 1;
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
version: "https://jsonfeed.org/version/1.1",
|
||||
title: "Rubicon's Rubicon",
|
||||
home_page_url: "https://rubicon.lightstands.xyz/",
|
||||
feed_url: "https://rubicon.lightstands.xyz/feed.json",
|
||||
next_url: hasNextPage
|
||||
? `https://rubicon.lightstands.xyz/feeds/${page.currentPage + 1}.json`
|
||||
: undefined,
|
||||
authors: authors.map((item) => {
|
||||
return {
|
||||
name: item.data.name,
|
||||
url: item.data.links?.[0].href,
|
||||
};
|
||||
}),
|
||||
items: await Promise.all(
|
||||
page.data.map(async (item) => {
|
||||
const { Content } = await render(item);
|
||||
return {
|
||||
id: item.id,
|
||||
context_html: await cont.renderToString(Content),
|
||||
url: new URL(postParamlink(item), "https://rubicon.lightstands.xyz").toString(),
|
||||
};
|
||||
}),
|
||||
),
|
||||
}, undefined, 2),
|
||||
);
|
||||
};
|
3
src/pages/index.astro
Normal file
3
src/pages/index.astro
Normal file
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
return Astro.rewrite("/page/1")
|
||||
---
|
73
src/pages/page/[page].astro
Normal file
73
src/pages/page/[page].astro
Normal file
|
@ -0,0 +1,73 @@
|
|||
---
|
||||
import type { GetStaticPathsOptions } from "astro";
|
||||
import { Image } from "astro:assets";
|
||||
import { getCollection } from "astro:content";
|
||||
import AuthorCard from "~/components/AuthorCard.astro";
|
||||
import Pager from "~/components/Pager.astro";
|
||||
import PostList from "~/components/PostList.astro";
|
||||
import TagListCard from "~/components/TagListCard.astro";
|
||||
import IndexLayout from "~/layouts/IndexLayout.astro";
|
||||
import { getAllTags, getHotTags, shouldBeVisible } from "~/utils/posts";
|
||||
import JSONFeedIcon from "~/assets/jsonfeed.png"
|
||||
import { Icon } from "astro-icon/components";
|
||||
|
||||
export async function getStaticPaths({ paginate }: GetStaticPathsOptions) {
|
||||
const posts = (await getCollection("posts")).filter(shouldBeVisible).sort((a, b) => {
|
||||
return b.data.date.getTime() - a.data.date.getTime();
|
||||
});
|
||||
return paginate(posts, {});
|
||||
}
|
||||
|
||||
const { page } = Astro.props;
|
||||
|
||||
const posts = await getCollection("posts");
|
||||
const tags = getAllTags(posts);
|
||||
const hotTags = getHotTags(posts, tags);
|
||||
|
||||
const defaultAuthors = (await getCollection("authors")).filter(
|
||||
(x) => x.data.default
|
||||
)!;
|
||||
---
|
||||
|
||||
<IndexLayout title="Rubicon's Rubicon">
|
||||
<link
|
||||
slot="head"
|
||||
rel="alternate"
|
||||
title="Rubicon's Rubicon Feed"
|
||||
type="application/feed+json"
|
||||
href="/feed.json"
|
||||
/>
|
||||
<main>
|
||||
<PostList posts={page.data} />
|
||||
<div style="display: flex; justify-content: center; margin: 16px;">
|
||||
<Pager {...page} urlForPage={(n) => `/page/${n}`} />
|
||||
</div>
|
||||
</main>
|
||||
<div
|
||||
style="display: flex; flex-flow: column nowrap; gap: 16px; height: fit-content; position: sticky; top: 0;"
|
||||
>
|
||||
{defaultAuthors.map((item) => <AuthorCard {...item.data} />)}
|
||||
<TagListCard title="所有标签" tags={tags} hotTags={hotTags} />
|
||||
<div style="display: flex; gap: 8px; flex-flow: row wrap;">
|
||||
<a href="/feed.json" style="display: inline-flex; align-items: center; gap: 8px" data-astro-prefetch="false">
|
||||
使用JSON Feed订阅此网站
|
||||
<Image
|
||||
src={JSONFeedIcon}
|
||||
alt="JSON Feed图标"
|
||||
height={16}
|
||||
width={16}
|
||||
style={{width: "1em", height: "1em"}}
|
||||
/>
|
||||
</a>
|
||||
|
||||
<a href="/atom.xml" style="display: inline-flex; align-items: center; gap: 8px" data-astro-prefetch="false">
|
||||
使用Atom订阅此网站
|
||||
<Icon name="mdi:rss-feed"/>
|
||||
</a>
|
||||
|
||||
<a href="/external-resource-usage/">
|
||||
External Resource Usage
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</IndexLayout>
|
18
src/pages/tags/[name].astro
Normal file
18
src/pages/tags/[name].astro
Normal file
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
import type { GetStaticPaths } from "astro";
|
||||
import { getCollection } from "astro:content";
|
||||
import { getAllTags } from "~/utils/posts";
|
||||
|
||||
export const getStaticPaths = (async () => {
|
||||
const posts = await getCollection("posts");
|
||||
const tags = getAllTags(posts);
|
||||
return tags.map((t) => ({
|
||||
params: { name: t },
|
||||
props: { name: t },
|
||||
}));
|
||||
}) satisfies GetStaticPaths;
|
||||
|
||||
const { name } = Astro.props;
|
||||
|
||||
return Astro.rewrite(`/tags/${name}/1`);
|
||||
---
|
49
src/pages/tags/[name]/[page].astro
Normal file
49
src/pages/tags/[name]/[page].astro
Normal file
|
@ -0,0 +1,49 @@
|
|||
---
|
||||
import type { GetStaticPaths } from "astro";
|
||||
import { getCollection } from "astro:content";
|
||||
import AuthorCard from "~/components/AuthorCard.astro";
|
||||
import Pager from "~/components/Pager.astro";
|
||||
import PostList from "~/components/PostList.astro";
|
||||
import TagListCard from "~/components/TagListCard.astro";
|
||||
import IndexLayout from "~/layouts/IndexLayout.astro";
|
||||
import { getAllTags, getHotTags, shouldBeVisible } from "~/utils/posts";
|
||||
|
||||
export const getStaticPaths = (async ({ paginate }) => {
|
||||
const posts = (await getCollection("posts")).filter(shouldBeVisible);
|
||||
const tags = getAllTags(posts);
|
||||
const postsByTag = tags.map((t) => {
|
||||
return posts.filter((p) => p.data.tags?.includes(t));
|
||||
});
|
||||
return tags.flatMap((name, idx) => {
|
||||
return paginate(postsByTag[idx], {
|
||||
params: { name },
|
||||
props: { name },
|
||||
});
|
||||
});
|
||||
}) satisfies GetStaticPaths;
|
||||
|
||||
const { page, name } = Astro.props;
|
||||
|
||||
const posts = (await getCollection("posts")).filter(shouldBeVisible);
|
||||
const tags = getAllTags(posts);
|
||||
const hotTags = getHotTags(posts, tags);
|
||||
|
||||
const defaultAuthors = (await getCollection("authors")).filter(
|
||||
(x) => x.data.default
|
||||
)!;
|
||||
---
|
||||
|
||||
<IndexLayout title={`标记为“${name}”的文章`}>
|
||||
<main>
|
||||
<PostList posts={page.data} />
|
||||
<div style="display: flex; justify-content: center; margin: 16px;">
|
||||
<Pager {...page} urlForPage={(n) => `/page/${n}`} />
|
||||
</div>
|
||||
</main>
|
||||
<div
|
||||
style="display: flex; flex-flow: column nowrap; gap: 16px; height: fit-content; position: sticky; top: 0;"
|
||||
>
|
||||
{defaultAuthors.map((item) => <AuthorCard {...item.data} />)}
|
||||
<TagListCard title="所有标签" tags={tags} hotTags={hotTags} />
|
||||
</div>
|
||||
</IndexLayout>
|
Loading…
Add table
Add a link
Reference in a new issue