diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 5e3a08d..e134233 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -23,10 +23,11 @@ export interface LayoutProps extends JSX.ElementChildrenAttribute { readonly host: string; readonly title?: string; readonly activityLink?: string | URL; + readonly feedLink?: string | URL; } export function Layout( - { bot, host, title, activityLink, children }: LayoutProps, + { bot, host, title, activityLink, feedLink, children }: LayoutProps, ) { const handle = `@${bot.username}@${host}`; const cssFilename = bot.pages.color === "azure" @@ -47,8 +48,17 @@ export function Layout( rel="alternate" type="application/activity+json" href={activityLink.toString()} + title="ActivityPub" /> )} + {feedLink && ( + + )} . /** @jsx react-jsx */ /** @jsxImportSource @hono/hono/jsx */ +import type { Context } from "@fedify/fedify/federation"; import { type Announce, type Create, + getActorHandle, Image, Link, type Object, PUBLIC_COLLECTION, } from "@fedify/fedify/vocab"; import { Hono } from "@hono/hono"; +import { unescape } from "@std/html/entities"; import type { BotImpl } from "./bot-impl.ts"; import { Layout } from "./components/Layout.tsx"; import { Message } from "./components/Message.tsx"; -import { getMessageClass, isMessageObject } from "./message-impl.ts"; +import { getMessageClass, isMessageObject, textXss } from "./message-impl.ts"; import type { MessageClass } from "./message.ts"; import type { Uuid } from "./repository.ts"; @@ -42,8 +45,6 @@ export interface Env { export const app = new Hono(); -const WINDOW = 15; - app.get("/", async (c) => { const { bot } = c.env; const ctx = bot.federation.createContext(c.req.raw, c.env.contextData); @@ -73,38 +74,24 @@ app.get("/", async (c) => { properties[name] = valueHtml; } const offset = c.req.query("offset"); - let posts = await Array.fromAsync( - bot.repository.getMessages({ - order: "newest", - until: offset ? Temporal.Instant.from(offset) : undefined, - limit: WINDOW * 2, - }), + const { posts: messages, nextPost } = await getPosts( + bot, + ctx, + offset ? Temporal.Instant.from(offset) : undefined, ); - let lastPost: Announce | Create | undefined = posts[posts.length - 1]; - posts = posts.filter(isPublic); - while (lastPost != null && posts.length < WINDOW + 1) { - const limit = (WINDOW - posts.length) * 2; - const nextPosts = bot.repository.getMessages({ - order: "newest", - until: lastPost.published ?? (await lastPost.getObject(ctx))?.published ?? - undefined, - limit, - }); - lastPost = undefined; - for await (const post of nextPosts) { - if (isPublic(post) && posts.length < WINDOW + 1) posts.push(post); - lastPost = post; - } + const activityLink = ctx.getActorUri(bot.identifier); + const feedLink = new URL("/feed.xml", url); + let nextLink: URL | undefined; + if (nextPost?.published != null) { + nextLink = new URL("/", url); + nextLink.searchParams.set("offset", nextPost.published.toString()); } - const nextPost: Object | null = await posts[WINDOW]?.getObject(ctx); - posts = posts.slice(0, WINDOW); - const messages = (await Promise.all(posts.map((p) => p.getObject(ctx)))) - .filter(isMessageObject); return c.html(
{image && ( @@ -132,6 +119,27 @@ app.get("/", async (c) => {

{handle} ·{" "} + + + + + + {" "} + ·{" "} {followersCount === 1 ? `1 follower` @@ -143,6 +151,7 @@ app.get("/", async (c) => { ? `1 post` : `${postsCount.toLocaleString("en")} posts`} + {" "}

{summary && @@ -175,13 +184,9 @@ app.get("/", async (c) => {