Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ To be released.
- Added [`GET /api/v1/blocks`] API to Mastodon comapatiblity layer. This API
returns a list of accounts that are blocked by the authenticated user.

- On profile page, backward pagination (newer posts) is now available.
[[#104], [#105] by Okuto Oyama]

- On profile page, images are no more captioned using `<figcaption>` but
use only `alt` attribute for accessibility. [[#99], [#100] by Okuto Oyama]

Expand All @@ -33,6 +36,8 @@ To be released.
[#99]: https://github.com/fedify-dev/hollo/issues/99
[#100]: https://github.com/fedify-dev/hollo/pull/100
[#103]: https://github.com/fedify-dev/hollo/issues/103
[#104]: https://github.com/fedify-dev/hollo/issues/104
[#105]: https://github.com/fedify-dev/hollo/pull/105
[`GET /api/v1/mutes`]: https://docs.joinmastodon.org/methods/mutes/#get
[`GET /api/v1/blocks`]: https://docs.joinmastodon.org/methods/blocks/#get

Expand Down
45 changes: 37 additions & 8 deletions src/pages/profile/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { and, desc, eq, lte, or } from "drizzle-orm";
import { and, count, desc, eq, or } from "drizzle-orm";
import { Hono } from "hono";
import xss from "xss";
import { Layout } from "../../components/Layout.tsx";
Expand Down Expand Up @@ -26,7 +26,7 @@ const profile = new Hono();

profile.route("/:id{[-a-f0-9]+}", profilePost);

const WINDOW = 30;
const PAGE_SIZE = 30;

profile.get<"/:handle">(async (c) => {
let handle = c.req.param("handle");
Expand All @@ -39,14 +39,38 @@ profile.get<"/:handle">(async (c) => {
const contStr = c.req.query("cont");
const cont = contStr == null || contStr.trim() === "" ? undefined : contStr;
if (cont != null && !isUuid(cont)) return c.notFound();
const pageStr = c.req.query("page");
if (
pageStr !== undefined &&
(Number.isNaN(Number.parseInt(pageStr)) || Number.parseInt(pageStr) < 1)
) {
return c.notFound();
}
const page =
pageStr !== undefined && !Number.isNaN(Number.parseInt(pageStr))
? Number.parseInt(pageStr)
: 1;
const [{ totalPosts }] = await db
.select({ totalPosts: count() })
.from(posts)
.where(
and(
eq(posts.accountId, owner.id),
or(eq(posts.visibility, "public"), eq(posts.visibility, "unlisted")),
),
);
const maxPage = Math.ceil(totalPosts / PAGE_SIZE);
if (page > maxPage) {
return c.notFound();
}
const postList = await db.query.posts.findMany({
where: and(
eq(posts.accountId, owner.id),
or(eq(posts.visibility, "public"), eq(posts.visibility, "unlisted")),
...(cont == null ? [] : [lte(posts.id, cont)]),
),
orderBy: desc(posts.id),
limit: WINDOW + 1,
limit: PAGE_SIZE,
offset: (page - 1) * PAGE_SIZE,
with: {
account: true,
media: true,
Expand Down Expand Up @@ -133,12 +157,13 @@ profile.get<"/:handle">(async (c) => {
const atomUrl = new URL(c.req.url);
atomUrl.pathname += "/atom.xml";
atomUrl.search = "";
const newerUrl = page > 1 ? `?page=${page - 1}` : undefined;
const olderUrl =
postList.length > WINDOW ? `?cont=${postList[WINDOW].id}` : undefined;
postList.length === PAGE_SIZE ? `?page=${page + 1}` : undefined;
return c.html(
<ProfilePage
accountOwner={owner}
posts={postList.slice(0, WINDOW)}
posts={postList.slice(0, PAGE_SIZE)}
pinnedPosts={pinnedPostList
.map((p) => p.post)
.filter(
Expand All @@ -147,6 +172,7 @@ profile.get<"/:handle">(async (c) => {
featuredTags={featuredTagList}
atomUrl={atomUrl.href}
olderUrl={olderUrl}
newerUrl={newerUrl}
/>,
);
});
Expand Down Expand Up @@ -224,6 +250,7 @@ interface ProfilePageProps {
readonly featuredTags: FeaturedTag[];
readonly atomUrl: string;
readonly olderUrl?: string;
readonly newerUrl?: string;
}

function ProfilePage({
Expand All @@ -233,6 +260,7 @@ function ProfilePage({
featuredTags,
atomUrl,
olderUrl,
newerUrl,
}: ProfilePageProps) {
return (
<Layout
Expand Down Expand Up @@ -267,8 +295,9 @@ function ProfilePage({
{posts.map((post) => (
<PostView post={post} />
))}
<div style="text-align: right;">
{olderUrl && <a href={olderUrl}>Older &rarr;</a>}
<div style={{ display: "flex", justifyContent: "space-between" }}>
<div>{newerUrl && <a href={newerUrl}>&larr; Newer</a>}</div>
<div>{olderUrl && <a href={olderUrl}>Older &rarr;</a>}</div>
</div>
</Layout>
);
Expand Down