diff --git a/pom.xml b/pom.xml index d35ef5ee..32366436 100644 --- a/pom.xml +++ b/pom.xml @@ -418,6 +418,11 @@ + + com.rometools + rome-modules + ${rome.version} + com.rometools rome-opml diff --git a/src/main/app/sass/components/_entry-list.scss b/src/main/app/sass/components/_entry-list.scss index 34f45c77..6d840fed 100644 --- a/src/main/app/sass/components/_entry-list.scss +++ b/src/main/app/sass/components/_entry-list.scss @@ -1,271 +1,279 @@ .main-content { - margin-left: 250px; - padding-left: 15px; + margin-left: 250px; + padding-left: 15px; } .full-screen .main-content { - width: 100%; - margin-left: 0; + width: 100%; + margin-left: 0; } .entryList { - padding-top: 50px; + padding-top: 50px; } /* entry list*/ .entrylist-header { - border-bottom: 1px solid #eee; + border-bottom: 1px solid #eee; } .entrylist-header h3 { - margin: 0; - line-height: 40px; + margin: 0; + line-height: 40px; } .entrylist-header a { - color: inherit; + color: inherit; } -.expanded .entry-header,.expanded .entry-body-content { - padding-left: 25px; - padding-right: 25px; +.expanded .entry-header, +.expanded .entry-body-content { + padding-left: 25px; + padding-right: 25px; } .expanded .entry-header { - max-width: 650px; + max-width: 650px; } .full-screen .expanded .entry-header { - max-width: 100%; + max-width: 100%; } -.rtl .entry-header,.rtl .entry-body-content,.rtl.entry-name { - direction: rtl; +.rtl .entry-header, +.rtl .entry-body-content, +.rtl.entry-name { + direction: rtl; } -.rtl ul,.rtl ol { - padding: 0; - margin: 0 25px 10px 0px; +.rtl ul, +.rtl ol { + padding: 0; + margin: 0 25px 10px 0px; } #feed-accordion .entry { - border-bottom: 1px solid #CCCCCC; + border-bottom: 1px solid #cccccc; } #feed-accordion .no-entries { - text-align: center; - font-weight: bold; - margin-top: 80px; + text-align: center; + font-weight: bold; + margin-top: 80px; } #feed-accordion .entry-heading { - background-color: #ebebeb; + background-color: #ebebeb; } #feed-accordion .unread .entry-heading:hover { - background-color: #ebebeb; + background-color: #ebebeb; } #feed-accordion .unread .entry-heading { - background-color: #fff; + background-color: #fff; } #feed-accordion .current.closed .entry-heading { - background-color: #ffc; + background-color: #ffc; } #feed-accordion .entry-heading-link { - color: black; - height: 32px; - display: block; - cursor: pointer; - padding: 6px 0px; + color: black; + height: 32px; + display: block; + cursor: pointer; + padding: 6px 0px; } #feed-accordion .entry-heading .feed-name { - color: #555; - display: block; - overflow: hidden; - white-space: nowrap; - position: absolute; - width: 145px; - text-overflow: ellipsis; + color: #555; + display: block; + overflow: hidden; + white-space: nowrap; + position: absolute; + width: 145px; + text-overflow: ellipsis; } #feed-accordion .entry-heading .entry-name { - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - margin-right: 150px; + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + margin-right: 150px; } #feed-accordion .unread .entry-heading .entry-name { - font-weight: bold; + font-weight: bold; } #feed-accordion .entry-heading .shrink { - margin-left: 150px; + margin-left: 150px; } #feed-accordion .entry-heading .entry-date { - display: block; - position: absolute; - right: 45px; + display: block; + position: absolute; + right: 45px; } #feed-accordion a.entry-heading-link:hover { - text-decoration: none; + text-decoration: none; } #feed-accordion .entry-external-link { - position: absolute; - right: 25px; - margin-top: -25px; - color: black; + position: absolute; + right: 25px; + margin-top: -25px; + color: black; } #feed-accordion .entry-external-link:hover { - text-decoration: none; + text-decoration: none; } #feed-accordion .entry-body .entry-title { - margin-top: 5px; - margin-bottom: 10px; - font-size: 130%; - font-weight: bold; + margin-top: 5px; + margin-bottom: 10px; + font-size: 130%; + font-weight: bold; } #feed-accordion .entry-body .entry-subtitle { - display: block; - font-size: 14px; - font-weight: normal; + display: block; + font-size: 14px; + font-weight: normal; } -#feed-accordion .entry-body-content { - max-width: 650px; - color: black; - padding-bottom: 10px; +#feed-accordion .entry-body-content { + max-width: 650px; + color: black; + padding-bottom: 10px; } -.full-screen #feed-accordion .entry-body-content { - max-width: 100%; +.full-screen #feed-accordion .entry-body-content { + max-width: 100%; } #feed-accordion .entry-enclosure { - clear: both; - padding-top: 10px; + clear: both; + padding-top: 10px; +} + +#feed-accordion .entry-media-description { + padding-top: 10px; } #feed-accordion .entry-buttons { - clear: both; - background-color: #fafafa; - padding: 3px 0px; - border-top: 1px solid #ebebeb; + clear: both; + background-color: #fafafa; + padding: 3px 0px; + border-top: 1px solid #ebebeb; } #feed-accordion .entry-buttons .checkbox.inline { - padding-top: 0px; - margin-left: 5px; + padding-top: 0px; + margin-left: 5px; } #feed-accordion .entry-buttons .keep-unread label { - display: inline; - font-weight: inherit; + display: inline; + font-weight: inherit; } #feed-accordion .share-buttons a { - color: #333333; - padding-left: 5px; + color: #333333; + padding-left: 5px; } #feed-accordion .share-buttons a:hover { - text-decoration: none; + text-decoration: none; } #feed-accordion .tags-panel { - margin-left: 30px; + margin-left: 30px; } -#feed-accordion .tags-panel .label{ - margin-left: 5px; +#feed-accordion .tags-panel .label { + margin-left: 5px; } .select2-container-multi .select2-choices .select2-search-field input { - padding: 2px + padding: 2px; } #feed-accordion .tag-input { - margin: 0 0 0 5px; - padding: 0; - width: 200px; + margin: 0 0 0 5px; + padding: 0; + width: 200px; } #feed-accordion .entry-buttons label { - margin-bottom: 0px; - font-size: 12px; + margin-bottom: 0px; + font-size: 12px; } #feed-accordion a.mark-up-to { - color: #333333; - position: absolute; - right: 30px; + color: #333333; + position: absolute; + right: 30px; } #feed-accordion a.mark-up-to:hover { - text-decoration: none; - cursor: pointer; + text-decoration: none; + cursor: pointer; } #feed-accordion .star { - text-decoration: none; - padding: 0px 5px; + text-decoration: none; + padding: 0px 5px; } #feed-accordion .icon-star-yellow { - color: gold; - text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black; + color: gold; + text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black; } #feed-accordion .icon-star-empty { - color: #555; + color: #555; } #feed-accordion.expanded .entry { - margin-bottom: 40px; - border: 1px solid #ddd; - box-shadow: 0 0 4px #e3e5eb; + margin-bottom: 40px; + border: 1px solid #ddd; + box-shadow: 0 0 4px #e3e5eb; } #feed-accordion.expanded .current { - border-left: 1px solid rgb(77, 144, 240); + border-left: 1px solid rgb(77, 144, 240); } #feed-accordion .current.entry-font-size-0 { - font-size: 14px; + font-size: 14px; } #feed-accordion .current.entry-font-size-1 { - font-size: 15px; + font-size: 15px; } #feed-accordion .current.entry-font-size-2 { - font-size: 16px; + font-size: 16px; } #feed-accordion .current.entry-font-size-3 { - font-size: 17px; + font-size: 17px; } #feed-accordion .current.entry-font-size-4 { - font-size: 18px; + font-size: 18px; } #feed-accordion .current.entry-font-size-5 { - font-size: 19px; + font-size: 19px; } #feed-accordion .highlight-search { - background-color: rgba(255, 255, 140, 0.5); - color: #333; -} \ No newline at end of file + background-color: rgba(255, 255, 140, 0.5); + color: #333; +} diff --git a/src/main/app/templates/feeds.view.html b/src/main/app/templates/feeds.view.html index a2f79117..6469357c 100644 --- a/src/main/app/templates/feeds.view.html +++ b/src/main/app/templates/feeds.view.html @@ -101,20 +101,26 @@ >
-
diff --git a/src/main/java/com/commafeed/backend/feed/FeedParser.java b/src/main/java/com/commafeed/backend/feed/FeedParser.java index 4b483af4..e34ec66d 100644 --- a/src/main/java/com/commafeed/backend/feed/FeedParser.java +++ b/src/main/java/com/commafeed/backend/feed/FeedParser.java @@ -10,9 +10,7 @@ import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Singleton; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - +import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.jdom2.Element; import org.jdom2.Namespace; @@ -22,6 +20,13 @@ import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedEntryContent; import com.google.common.collect.Iterables; +import com.rometools.modules.mediarss.MediaEntryModule; +import com.rometools.modules.mediarss.MediaModule; +import com.rometools.modules.mediarss.types.MediaGroup; +import com.rometools.modules.mediarss.types.Metadata; +import com.rometools.modules.mediarss.types.Thumbnail; +import com.rometools.rome.feed.synd.SyndCategory; +import com.rometools.rome.feed.synd.SyndContent; import com.rometools.rome.feed.synd.SyndEnclosure; import com.rometools.rome.feed.synd.SyndEntry; import com.rometools.rome.feed.synd.SyndFeed; @@ -30,6 +35,10 @@ import com.rometools.rome.feed.synd.SyndLinkImpl; import com.rometools.rome.io.FeedException; import com.rometools.rome.io.SyndFeedInput; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + @Slf4j @RequiredArgsConstructor(onConstructor = @__({ @Inject })) @Singleton @@ -86,15 +95,28 @@ public class FeedParser { FeedEntryContent content = new FeedEntryContent(); content.setContent(getContent(item)); - content.setCategories(FeedUtils.truncate( - item.getCategories().stream().map(c -> c.getName()).collect(Collectors.joining(", ")), 4096)); + content.setCategories(FeedUtils + .truncate(item.getCategories().stream().map(SyndCategory::getName).collect(Collectors.joining(", ")), 4096)); content.setTitle(getTitle(item)); content.setAuthor(StringUtils.trimToNull(item.getAuthor())); + SyndEnclosure enclosure = Iterables.getFirst(item.getEnclosures(), null); if (enclosure != null) { content.setEnclosureUrl(FeedUtils.truncate(enclosure.getUrl(), 2048)); content.setEnclosureType(enclosure.getType()); } + + MediaEntryModule module = (MediaEntryModule) item.getModule(MediaModule.URI); + if (module != null) { + Media media = getMedia(module); + if (media != null) { + content.setMediaDescription(media.getDescription()); + content.setMediaThumbnailUrl(FeedUtils.truncate(media.getThumbnailUrl(), 2048)); + content.setMediaThumbnailWidth(media.getThumbnailWidth()); + content.setMediaThumbnailHeight(media.getThumbnailHeight()); + } + } + entry.setContent(content); entries.add(entry); @@ -166,7 +188,7 @@ public class FeedParser { if (item.getContents().isEmpty()) { content = item.getDescription() == null ? null : item.getDescription().getValue(); } else { - content = item.getContents().stream().map(c -> c.getValue()).collect(Collectors.joining(System.lineSeparator())); + content = item.getContents().stream().map(SyndContent::getValue).collect(Collectors.joining(System.lineSeparator())); } return StringUtils.trimToNull(content); } @@ -184,6 +206,41 @@ public class FeedParser { return StringUtils.trimToNull(title); } + private Media getMedia(MediaEntryModule module) { + Media media = getMedia(module.getMetadata()); + if (media == null && ArrayUtils.isNotEmpty(module.getMediaGroups())) { + MediaGroup group = module.getMediaGroups()[0]; + media = getMedia(group.getMetadata()); + } + + return media; + } + + private Media getMedia(Metadata metadata) { + if (metadata == null) { + return null; + } + + Media media = new Media(); + media.setDescription(metadata.getDescription()); + + if (ArrayUtils.isNotEmpty(metadata.getThumbnail())) { + Thumbnail thumbnail = metadata.getThumbnail()[0]; + media.setThumbnailWidth(thumbnail.getWidth()); + media.setThumbnailHeight(thumbnail.getHeight()); + + if (thumbnail.getUrl() != null) { + media.setThumbnailUrl(thumbnail.getUrl().toString()); + } + } + + if (media.isEmpty()) { + return null; + } + + return media; + } + private String findHub(SyndFeed feed) { for (SyndLink l : feed.getLinks()) { if ("hub".equalsIgnoreCase(l.getRel())) { @@ -204,4 +261,16 @@ public class FeedParser { return null; } + @Data + private static class Media { + private String description; + private String thumbnailUrl; + private Integer thumbnailWidth; + private Integer thumbnailHeight; + + public boolean isEmpty() { + return description == null && thumbnailUrl == null; + } + } + } diff --git a/src/main/java/com/commafeed/backend/model/FeedEntryContent.java b/src/main/java/com/commafeed/backend/model/FeedEntryContent.java index 34ae0c69..3f005c4d 100644 --- a/src/main/java/com/commafeed/backend/model/FeedEntryContent.java +++ b/src/main/java/com/commafeed/backend/model/FeedEntryContent.java @@ -43,6 +43,17 @@ public class FeedEntryContent extends AbstractModel { @Column(length = 255) private String enclosureType; + @Lob + @Column(length = Integer.MAX_VALUE) + @Type(type = "org.hibernate.type.TextType") + private String mediaDescription; + + @Column(length = 2048) + private String mediaThumbnailUrl; + + private Integer mediaThumbnailWidth; + private Integer mediaThumbnailHeight; + @Column(length = 4096) private String categories; diff --git a/src/main/java/com/commafeed/backend/service/FeedEntryContentService.java b/src/main/java/com/commafeed/backend/service/FeedEntryContentService.java index d7bc7fdf..738c8134 100644 --- a/src/main/java/com/commafeed/backend/service/FeedEntryContentService.java +++ b/src/main/java/com/commafeed/backend/service/FeedEntryContentService.java @@ -3,8 +3,6 @@ package com.commafeed.backend.service; import javax.inject.Inject; import javax.inject.Singleton; -import lombok.RequiredArgsConstructor; - import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.StringUtils; @@ -12,6 +10,8 @@ import com.commafeed.backend.dao.FeedEntryContentDAO; import com.commafeed.backend.feed.FeedUtils; import com.commafeed.backend.model.FeedEntryContent; +import lombok.RequiredArgsConstructor; + @RequiredArgsConstructor(onConstructor = @__({ @Inject })) @Singleton public class FeedEntryContentService { @@ -35,6 +35,7 @@ public class FeedEntryContentService { content.setAuthor(FeedUtils.truncate(FeedUtils.handleContent(content.getAuthor(), baseUrl, true), 128)); content.setTitle(FeedUtils.truncate(FeedUtils.handleContent(content.getTitle(), baseUrl, true), 2048)); content.setContent(FeedUtils.handleContent(content.getContent(), baseUrl, false)); + content.setMediaDescription(FeedUtils.handleContent(content.getMediaDescription(), baseUrl, false)); result = content; feedEntryContentDAO.saveOrUpdate(result); } else { diff --git a/src/main/java/com/commafeed/frontend/model/Entry.java b/src/main/java/com/commafeed/frontend/model/Entry.java index 91e87325..2c631d8d 100644 --- a/src/main/java/com/commafeed/frontend/model/Entry.java +++ b/src/main/java/com/commafeed/frontend/model/Entry.java @@ -12,6 +12,7 @@ import com.commafeed.backend.feed.FeedUtils; import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedEntryContent; import com.commafeed.backend.model.FeedEntryStatus; +import com.commafeed.backend.model.FeedEntryTag; import com.commafeed.backend.model.FeedSubscription; import com.rometools.rome.feed.synd.SyndContent; import com.rometools.rome.feed.synd.SyndContentImpl; @@ -49,17 +50,25 @@ public class Entry implements Serializable { entry.setFeedUrl(sub.getFeed().getUrl()); entry.setFeedLink(sub.getFeed().getLink()); entry.setIconUrl(FeedUtils.getFaviconUrl(sub, publicUrl)); - entry.setTags(status.getTags().stream().map(t -> t.getName()).collect(Collectors.toList())); + entry.setTags(status.getTags().stream().map(FeedEntryTag::getName).collect(Collectors.toList())); if (content != null) { entry.setRtl(FeedUtils.isRTL(feedEntry)); entry.setTitle(content.getTitle()); entry.setContent(proxyImages ? FeedUtils.proxyImages(content.getContent(), publicUrl) : content.getContent()); entry.setAuthor(content.getAuthor()); + entry.setEnclosureType(content.getEnclosureType()); entry.setEnclosureUrl(proxyImages && StringUtils.contains(content.getEnclosureType(), "image") ? FeedUtils.proxyImage(content.getEnclosureUrl(), publicUrl) : content.getEnclosureUrl()); + + entry.setMediaDescription(content.getMediaDescription()); + entry.setMediaThumbnailUrl( + proxyImages ? FeedUtils.proxyImage(content.getMediaThumbnailUrl(), publicUrl) : content.getMediaThumbnailUrl()); + entry.setMediaThumbnailWidth(content.getMediaThumbnailWidth()); + entry.setMediaThumbnailHeight(content.getMediaThumbnailHeight()); + entry.setCategories(content.getCategories()); } @@ -116,6 +125,18 @@ public class Entry implements Serializable { @ApiModelProperty(value = "entry enclosure mime type, if any") private String enclosureType; + @ApiModelProperty(value = "entry media description, if any") + private String mediaDescription; + + @ApiModelProperty(value = "entry media thumbnail url, if any") + private String mediaThumbnailUrl; + + @ApiModelProperty(value = "entry media thumbnail width, if any") + private Integer mediaThumbnailWidth; + + @ApiModelProperty(value = "entry media thumbnail height, if any") + private Integer mediaThumbnailHeight; + @ApiModelProperty(value = "entry publication date", dataType = "number", required = true) private Date date; diff --git a/src/main/resources/changelogs/db.changelog-2.6.xml b/src/main/resources/changelogs/db.changelog-2.6.xml index 62149c04..23a0a002 100644 --- a/src/main/resources/changelogs/db.changelog-2.6.xml +++ b/src/main/resources/changelogs/db.changelog-2.6.xml @@ -8,4 +8,19 @@ + + + + + + + + + + + + + + +