Merge pull request #448 from black-puppydog/feed-pagination

Add simple pagination to user feeds.
This commit is contained in:
Christian Bundy 2020-05-24 10:43:55 -07:00 committed by GitHub
commit eb9e44981a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 114 additions and 12 deletions

View File

@ -264,11 +264,20 @@ router
}) })
.get("/author/:feed", async (ctx) => { .get("/author/:feed", async (ctx) => {
const { feed } = ctx.params; const { feed } = ctx.params;
const gt = Number(ctx.request.query["gt"] || -1);
const lt = Number(ctx.request.query["lt"] || -1);
if (lt > 0 && gt > 0 && gt >= lt)
throw new Error("Given search range is empty");
const author = async (feedId) => { const author = async (feedId) => {
const description = await about.description(feedId); const description = await about.description(feedId);
const name = await about.name(feedId); const name = await about.name(feedId);
const image = await about.image(feedId); const image = await about.image(feedId);
const messages = await post.fromPublicFeed(feedId); const messages = await post.fromPublicFeed(feedId, gt, lt);
const firstPost = await post.firstBy(feedId);
const lastPost = await post.latestBy(feedId);
const relationship = await friend.getRelationship(feedId); const relationship = await friend.getRelationship(feedId);
const avatarUrl = `/image/256/${encodeURIComponent(image)}`; const avatarUrl = `/image/256/${encodeURIComponent(image)}`;
@ -276,6 +285,8 @@ router
return authorView({ return authorView({
feedId, feedId,
messages, messages,
firstPost,
lastPost,
name, name,
description, description,
avatarUrl, avatarUrl,
@ -342,17 +353,27 @@ router
.get("/profile/", async (ctx) => { .get("/profile/", async (ctx) => {
const myFeedId = await meta.myFeedId(); const myFeedId = await meta.myFeedId();
const gt = Number(ctx.request.query["gt"] || -1);
const lt = Number(ctx.request.query["lt"] || -1);
if (lt > 0 && gt > 0 && gt >= lt)
throw new Error("Given search range is empty");
const description = await about.description(myFeedId); const description = await about.description(myFeedId);
const name = await about.name(myFeedId); const name = await about.name(myFeedId);
const image = await about.image(myFeedId); const image = await about.image(myFeedId);
const messages = await post.fromPublicFeed(myFeedId); const messages = await post.fromPublicFeed(myFeedId, gt, lt);
const firstPost = await post.firstBy(myFeedId);
const lastPost = await post.latestBy(myFeedId);
const avatarUrl = `/image/256/${encodeURIComponent(image)}`; const avatarUrl = `/image/256/${encodeURIComponent(image)}`;
ctx.body = await authorView({ ctx.body = await authorView({
feedId: myFeedId, feedId: myFeedId,
messages, messages,
firstPost,
lastPost,
name, name,
description, description,
avatarUrl, avatarUrl,

View File

@ -627,14 +627,43 @@ module.exports = ({ cooler, isPublic }) => {
}) })
); );
const getLimitPost = async (feedId, reverse) => {
const ssb = await cooler.open();
const source = ssb.createUserStream({ id: feedId, reverse: reverse });
const messages = await new Promise((resolve, reject) => {
pull(
source,
pull.filter((msg) => isDecrypted(msg) === false && isPost(msg)),
pull.take(1),
pull.collect((err, collectedMessages) => {
if (err) {
reject(err);
} else {
resolve(transform(ssb, collectedMessages, feedId));
}
})
);
});
return messages.length ? messages[0] : undefined;
};
const post = { const post = {
fromPublicFeed: async (feedId, customOptions = {}) => { firstBy: async (feedId) => {
return getLimitPost(feedId, false);
},
latestBy: async (feedId) => {
return getLimitPost(feedId, true);
},
fromPublicFeed: async (feedId, gt = -1, lt = -1, customOptions = {}) => {
const ssb = await cooler.open(); const ssb = await cooler.open();
const myFeedId = ssb.id; const myFeedId = ssb.id;
const options = configure({ id: feedId }, customOptions); let defaultOptions = { id: feedId };
if (lt >= 0) defaultOptions.lt = lt;
if (gt >= 0) defaultOptions.gt = gt;
defaultOptions.reverse = !(gt >= 0 && lt < 0);
const options = configure(defaultOptions, customOptions);
const { blocking } = await models.friend.getRelationship(feedId); const { blocking } = await models.friend.getRelationship(feedId);
// Avoid streaming any messages from this feed. If we used the social // Avoid streaming any messages from this feed. If we used the social
@ -661,7 +690,8 @@ module.exports = ({ cooler, isPublic }) => {
); );
}); });
return messages; if (!defaultOptions.reverse) return messages.reverse();
else return messages;
}, },
mentionsMe: async (customOptions = {}) => { mentionsMe: async (customOptions = {}) => {
const ssb = await cooler.open(); const ssb = await cooler.open();

View File

@ -62,6 +62,13 @@ const i18n = {
follow: "Follow", follow: "Follow",
block: "Block", block: "Block",
unblock: "Unblock", unblock: "Unblock",
newerPosts: "Newer posts",
olderPosts: "Older posts",
feedRangeEmpty: "The given range is empty for this feed. Try viewing the ",
seeFullFeed: "full feed",
feedEmpty: "The local client has never seen posts from this account.",
beginningOfFeed: "This is the beginning of the feed",
noNewerPosts: "No newer posts have been received yet.",
relationshipFollowing: "You are following", relationshipFollowing: "You are following",
relationshipYou: "This is you", relationshipYou: "This is you",
relationshipBlocking: "You are blocking", relationshipBlocking: "You are blocking",

View File

@ -505,13 +505,15 @@ exports.editProfileView = ({ name, description }) =>
); );
/** /**
* @param {{avatarUrl: string, description: string, feedId: string, messages: any[], name: string, relationship: object}} input * @param {{avatarUrl: string, description: string, feedId: string, messages: any[], name: string, relationship: object, firstPost: object, lastPost: object}} input
*/ */
exports.authorView = ({ exports.authorView = ({
avatarUrl, avatarUrl,
description, description,
feedId, feedId,
messages, messages,
firstPost,
lastPost,
name, name,
relationship, relationship,
}) => { }) => {
@ -600,11 +602,51 @@ exports.authorView = ({
) )
); );
return template( const linkUrl = relationship.me
i18n.profile, ? "/profile/"
prefix, : `/author/${encodeURIComponent(feedId)}/`;
messages.map((msg) => post({ msg }))
); let items = messages.map((msg) => post({ msg }));
if (items.length === 0) {
if (lastPost === undefined) {
items.push(section(div(span(i18n.feedEmpty))));
} else {
items.push(
section(
div(
span(i18n.feedRangeEmpty),
a({ href: `${linkUrl}` }, i18n.seeFullFeed)
)
)
);
}
} else {
const highestSeqNum = messages[0].value.sequence;
const lowestSeqNum = messages[messages.length - 1].value.sequence;
let newerPostsLink;
if (lastPost !== undefined && highestSeqNum < lastPost.value.sequence)
newerPostsLink = a(
{ href: `${linkUrl}?gt=${highestSeqNum}` },
i18n.newerPosts
);
else newerPostsLink = span(i18n.newerPosts, { title: i18n.noNewerPosts });
let olderPostsLink;
if (lowestSeqNum > firstPost.value.sequence)
olderPostsLink = a(
{ href: `${linkUrl}?lt=${lowestSeqNum}` },
i18n.olderPosts
);
else
olderPostsLink = span(i18n.olderPosts, { title: i18n.beginningOfFeed });
const pagination = section(
{ class: "message" },
footer(div(newerPostsLink, olderPostsLink), br())
);
items.unshift(pagination);
items.push(pagination);
}
return template(i18n.profile, prefix, items);
}; };
exports.commentView = async ({ messages, myFeedId, parentMessage }) => { exports.commentView = async ({ messages, myFeedId, parentMessage }) => {

View File

@ -10,6 +10,8 @@ const paths = [
"/inbox", "/inbox",
"/mentions", "/mentions",
"/profile", "/profile",
"/profile?gt=0",
"/profile?lt=100",
"/profile/edit", "/profile/edit",
"/public/latest", "/public/latest",
"/public/latest/extended", "/public/latest/extended",