use of org.moera.node.data.PublicPage in project moera-node by MoeraOrg.
the class PublicPageOperations method createPagination.
public List<PaginationItem> createPagination(PublicPage page) {
if (page == null) {
return null;
}
UUID entryId = page.getEntry() != null ? page.getEntry().getId() : null;
int current = countNumber(entryId, page.getBeforeMoment());
int last = countTotal(entryId);
if (last <= 1) {
return null;
}
int tillLast = last - current;
tillLast = Math.min(tillLast, 2);
int rangeFirst = current + tillLast - 4;
rangeFirst = Math.max(rangeFirst, 1);
PublicPage firstPage = findPages(entryId, null, rangeFirst - 1, 1).getContent().get(0);
PublicPage lastPage = findPages(entryId, null, last - 1, 1).getContent().get(0);
List<PublicPage> pages = findPages(entryId, firstPage.getBeforeMoment(), 0, 5).getContent();
int rangeLast = rangeFirst + pages.size() - 1;
LinkedList<PaginationItem> items = new LinkedList<>();
for (int i = 0; i < pages.size(); i++) {
items.add(PaginationItem.pageLink(rangeFirst + i, pages.get(i).getBeforeMoment(), rangeFirst + i == current));
}
if (rangeFirst > 2) {
items.addFirst(PaginationItem.pageDots());
}
if (rangeFirst > 1) {
items.addFirst(PaginationItem.pageLink(1, firstPage.getBeforeMoment(), false));
}
if (last - rangeLast > 1) {
items.addLast(PaginationItem.pageDots());
}
if (last > rangeLast) {
items.addLast(PaginationItem.pageLink(last, lastPage.getBeforeMoment(), false));
}
long prevMoment = 0;
long nextMoment = 0;
boolean afterCurrent = false;
for (PaginationItem item : items) {
if (item.isDots()) {
continue;
}
if (item.isActive()) {
afterCurrent = true;
continue;
}
if (!afterCurrent) {
prevMoment = item.getMoment();
} else {
nextMoment = item.getMoment();
break;
}
}
if (prevMoment != 0) {
items.addFirst(PaginationItem.pageLink("← Previous", prevMoment, false));
}
if (nextMoment != 0) {
items.addLast(PaginationItem.pageLink("Next →", nextMoment, false));
}
return items;
}
use of org.moera.node.data.PublicPage in project moera-node by MoeraOrg.
the class PublicPageOperations method updatePublicPages.
protected void updatePublicPages(UUID entryId, long moment) {
PublicPage firstPage = findByBeforeMoment(entryId, Long.MAX_VALUE);
if (firstPage == null) {
firstPage = new PublicPage();
firstPage.setNodeId(requestContext.nodeId());
firstPage.setEntry(findEntryById(entryId));
firstPage.setAfterMoment(Long.MIN_VALUE);
firstPage.setBeforeMoment(Long.MAX_VALUE);
publicPageRepository.save(firstPage);
return;
}
long after = firstPage.getAfterMoment();
if (moment > after) {
int count = countInRange(entryId, after, Long.MAX_VALUE);
if (count >= publicPageMaxSize) {
long median = findMomentsInRange(entryId, after, Long.MAX_VALUE, PageRequest.of(count - publicPageAvgSize, 1, Sort.by(Sort.Direction.DESC, "moment"))).getContent().get(0);
firstPage.setAfterMoment(median);
PublicPage secondPage = new PublicPage();
secondPage.setNodeId(requestContext.nodeId());
secondPage.setEntry(findEntryById(entryId));
secondPage.setAfterMoment(after);
secondPage.setBeforeMoment(median);
publicPageRepository.save(secondPage);
}
return;
}
PublicPage lastPage = findByAfterMoment(entryId, Long.MIN_VALUE);
long end = lastPage.getBeforeMoment();
if (moment <= end) {
int count = countInRange(entryId, Long.MIN_VALUE, end);
if (count >= publicPageMaxSize) {
long median = findMomentsInRange(entryId, Long.MIN_VALUE, end, PageRequest.of(publicPageAvgSize + 1, 1, Sort.by(Sort.Direction.DESC, "moment"))).getContent().get(0);
lastPage.setBeforeMoment(median);
PublicPage prevPage = new PublicPage();
prevPage.setNodeId(requestContext.nodeId());
prevPage.setEntry(findEntryById(entryId));
prevPage.setAfterMoment(median);
prevPage.setBeforeMoment(end);
publicPageRepository.save(prevPage);
}
}
}
use of org.moera.node.data.PublicPage in project moera-node by MoeraOrg.
the class RssController method rss.
@GetMapping("/rss")
@Transactional
@ResponseBody
public SyndFeed rss() {
RequestContext rcp = requestContext.getPublic();
PublicPage publicPage = publicPageRepository.findContaining(rcp.nodeId(), Long.MAX_VALUE);
List<Story> stories = Collections.emptyList();
if (publicPage != null) {
stories = storyRepository.findInRange(rcp.nodeId(), Feed.TIMELINE, publicPage.getAfterMoment(), publicPage.getBeforeMoment()).stream().filter(t -> t.getEntry().isMessage()).sorted(Collections.reverseOrder(Comparator.comparingLong(Story::getMoment))).collect(Collectors.toList());
}
SyndFeed feed = new SyndFeedImpl();
feed.setFeedType("rss_2.0");
String name = rcp.fullName() != null ? rcp.fullName() : rcp.nodeName();
String title = !ObjectUtils.isEmpty(name) ? name + " - Moera" : "Moera";
feed.setTitle(title);
feed.setLink(rcp.getSiteUrl() + "/");
if (rcp.avatarId() != null) {
SyndImage image = new SyndImageImpl();
image.setTitle(title);
image.setUrl(rcp.getSiteUrl() + "/moera/media/" + new AvatarImage(rcp.getAvatar()).getPath());
image.setLink(rcp.getSiteUrl() + "/");
feed.setImage(image);
}
feed.setDescription(title);
feed.setLanguage("en-us");
feed.setPublishedDate(!stories.isEmpty() ? stories.get(0).getCreatedAt() : Util.now());
feed.setGenerator("moera-node");
feed.setWebMaster(buildWebmaster());
feed.setEntries(stories.stream().map(this::buildEntry).collect(Collectors.toList()));
return feed;
}
use of org.moera.node.data.PublicPage in project moera-node by MoeraOrg.
the class TimelineUiController method timeline.
@GetMapping("/timeline")
@VirtualPage
@Transactional
public String timeline(@RequestParam(required = false) Long before, HttpServletResponse response, Model model) {
String canonicalUrl = "/timeline" + (before != null ? "?before=" + before : "");
before = before != null ? before : Long.MAX_VALUE;
List<StoryInfo> stories = Collections.emptyList();
PublicPage publicPage = publicPageRepository.findContaining(requestContext.nodeId(), before);
if (publicPage != null) {
stories = storyRepository.findInRange(requestContext.nodeId(), Feed.TIMELINE, publicPage.getAfterMoment(), publicPage.getBeforeMoment()).stream().filter(t -> t.getEntry().isMessage()).map(s -> StoryInfo.build(s, false, t -> PostingInfo.forUi((Posting) t.getEntry()))).sorted(Comparator.comparing(StoryInfo::getMoment).reversed()).collect(Collectors.toList());
}
model.addAttribute("pageTitle", titleBuilder.build("Timeline"));
model.addAttribute("menuIndex", "timeline");
model.addAttribute("canonicalUrl", canonicalUrl);
model.addAttribute("anchor", "m" + before);
model.addAttribute("stories", stories);
model.addAttribute("pagination", timelinePublicPageOperations.createPagination(publicPage));
model.addAttribute("ogUrl", requestContext.getSiteUrl());
return "timeline";
}
use of org.moera.node.data.PublicPage in project moera-node by MoeraOrg.
the class TimelineUiController method posting.
@GetMapping("/post/{id}")
@Transactional
public String posting(@PathVariable UUID id, @RequestParam(required = false) Long before, @RequestParam(name = "comment", required = false) UUID commentId, @RequestParam(name = "media", required = false) UUID mediaId, HttpServletResponse response, Model model) {
UriComponentsBuilder builder = UriComponentsBuilder.fromPath("/post/" + id);
if (commentId != null) {
builder = builder.queryParam("comment", commentId);
}
String canonicalUrl = builder.build().toUriString();
if (mediaId != null) {
builder = builder.queryParam("media", mediaId);
}
VirtualPageHeader.put(response, builder.build().toUriString());
if (requestContext.isBrowserExtension()) {
return null;
}
Posting posting = postingRepository.findFullByNodeIdAndId(requestContext.nodeId(), id).orElse(null);
if (posting == null || !posting.isMessage() || posting.getParentMedia() != null) {
throw new PageNotFoundException();
}
List<Story> stories = storyRepository.findByEntryId(requestContext.nodeId(), id);
model.addAttribute("pageTitle", titleBuilder.build(posting.getCurrentRevision().getHeading()));
model.addAttribute("menuIndex", "timeline");
model.addAttribute("posting", PostingInfo.forUi(posting, stories));
model.addAttribute("canonicalUrl", canonicalUrl);
model.addAttribute("openComments", commentId != null || before != null);
model.addAttribute("openMediaPostingId", id.toString());
model.addAttribute("openMediaCommentId", commentId != null ? commentId.toString() : null);
model.addAttribute("openMediaId", Objects.toString(mediaId, null));
Comment comment = commentId != null && posting.isOriginal() ? commentRepository.findFullByNodeIdAndId(requestContext.nodeId(), commentId).orElse(null) : null;
if (posting.isOriginal()) {
before = comment != null ? comment.getMoment() : before;
before = before != null ? before : Long.MIN_VALUE + 1;
List<CommentInfo> comments = Collections.emptyList();
PublicPage publicPage = publicPageRepository.findContainingForEntry(requestContext.nodeId(), id, before);
if (publicPage != null) {
comments = commentRepository.findInRange(requestContext.nodeId(), id, publicPage.getAfterMoment(), publicPage.getBeforeMoment()).stream().filter(Comment::isMessage).map(CommentInfo::forUi).sorted(Comparator.comparing(CommentInfo::getMoment)).collect(Collectors.toList());
}
if (commentId != null) {
model.addAttribute("anchor", "comment-" + commentId);
}
model.addAttribute("comments", comments);
model.addAttribute("commentId", Objects.toString(commentId, null));
model.addAttribute("pagination", commentPublicPageOperations.createPagination(publicPage));
} else {
model.addAttribute("originalHref", NamingCache.getRedirector(posting.getReceiverName(), entryLocation(posting.getReceiverEntryId(), null)));
model.addAttribute("commentsHref", NamingCache.getRedirector(posting.getReceiverName(), entryLocation(posting.getReceiverEntryId(), commentId)));
}
model.addAttribute("ogUrl", requestContext.getSiteUrl() + entryLocation(posting.getId(), commentId));
model.addAttribute("ogType", "article");
Entry entry = comment != null ? comment : posting;
String subject = new Body(entry.getCurrentRevision().getBody()).getSubject();
model.addAttribute("ogTitle", !ObjectUtils.isEmpty(subject) ? subject : "(no title)");
EntryAttachment attachment = entry.getCurrentRevision().getAttachments().stream().sorted(Comparator.comparingInt(EntryAttachment::getOrdinal)).filter(ea -> mediaId == null || ea.getMediaFileOwner().getId().equals(mediaId)).filter(// an image
ea -> ea.getMediaFileOwner().getMediaFile().getSizeX() != null).findFirst().orElse(null);
if (attachment != null) {
PrivateMediaFileInfo image = new PrivateMediaFileInfo(attachment.getMediaFileOwner(), posting.getReceiverName());
model.addAttribute("ogImage", requestContext.getSiteUrl() + "/moera/media/" + image.getPath());
model.addAttribute("ogImageType", attachment.getMediaFileOwner().getMediaFile().getMimeType());
model.addAttribute("ogImageWidth", image.getWidth());
model.addAttribute("ogImageHeight", image.getHeight());
} else if (entry.getOwnerAvatarMediaFile() != null) {
AvatarImage avatarImage = new AvatarImage(entry.getOwnerAvatarMediaFile(), entry.getOwnerAvatarShape());
model.addAttribute("ogImage", requestContext.getSiteUrl() + "/moera/media/" + avatarImage.getPath());
model.addAttribute("ogImageType", avatarImage.getMediaFile().getMimeType());
model.addAttribute("ogImageWidth", avatarImage.getWidth());
model.addAttribute("ogImageHeight", avatarImage.getHeight());
}
String description = entry.getCurrentRevision().getDescription();
description = !ObjectUtils.isEmpty(description) ? description : entry.getCurrentRevision().getHeading();
model.addAttribute("ogDescription", description);
var createdAt = entry.getReceiverCreatedAt() != null ? entry.getReceiverCreatedAt() : entry.getCreatedAt();
model.addAttribute("ogArticlePublishedTime", createdAt.toInstant().toString());
if (entry.getEditedAt() != null) {
var editedAt = entry.getReceiverEditedAt() != null ? entry.getReceiverEditedAt() : entry.getEditedAt();
model.addAttribute("ogArticleModifiedTime", editedAt.toInstant().toString());
}
return "posting";
}
Aggregations