Merge pull request #448 from black-puppydog/feed-pagination
Add simple pagination to user feeds.
This commit is contained in:
commit
eb9e44981a
25
src/index.js
25
src/index.js
|
@ -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,
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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 }) => {
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Loading…
Reference in New Issue