diff --git a/src/main/java/com/commafeed/backend/dao/FeedEntryStatusDAO.java b/src/main/java/com/commafeed/backend/dao/FeedEntryStatusDAO.java index 00311c8c..f99fbacf 100644 --- a/src/main/java/com/commafeed/backend/dao/FeedEntryStatusDAO.java +++ b/src/main/java/com/commafeed/backend/dao/FeedEntryStatusDAO.java @@ -31,6 +31,8 @@ import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedEntryContent_; import com.commafeed.backend.model.FeedEntryStatus; import com.commafeed.backend.model.FeedEntryStatus_; +import com.commafeed.backend.model.FeedEntryTag; +import com.commafeed.backend.model.FeedEntryTag_; import com.commafeed.backend.model.FeedEntry_; import com.commafeed.backend.model.FeedSubscription; import com.commafeed.backend.model.Models; @@ -47,6 +49,10 @@ public class FeedEntryStatusDAO extends GenericDAO { private static final String ALIAS_STATUS = "status"; private static final String ALIAS_ENTRY = "entry"; + private static final String ALIAS_TAG = "tag"; + + @Inject + FeedEntryTagDAO feedEntryTagDAO; private static final Comparator STATUS_COMPARATOR_DESC = new Comparator() { @Override @@ -63,7 +69,7 @@ public class FeedEntryStatusDAO extends GenericDAO { @Inject ApplicationSettingsService applicationSettingsService; - public FeedEntryStatus getStatus(FeedSubscription sub, FeedEntry entry) { + public FeedEntryStatus getStatus(User user, FeedSubscription sub, FeedEntry entry) { CriteriaQuery query = builder.createQuery(getType()); Root root = query.from(getType()); @@ -76,14 +82,14 @@ public class FeedEntryStatusDAO extends GenericDAO { List statuses = em.createQuery(query).getResultList(); FeedEntryStatus status = Iterables.getFirst(statuses, null); - return handleStatus(status, sub, entry); + return handleStatus(user, status, sub, entry); } - private FeedEntryStatus handleStatus(FeedEntryStatus status, FeedSubscription sub, FeedEntry entry) { + private FeedEntryStatus handleStatus(User user, FeedEntryStatus status, FeedSubscription sub, FeedEntry entry) { if (status == null) { Date unreadThreshold = applicationSettingsService.getUnreadThreshold(); boolean read = unreadThreshold == null ? false : entry.getUpdated().before(unreadThreshold); - status = new FeedEntryStatus(sub.getUser(), sub, entry); + status = new FeedEntryStatus(user, sub, entry); status.setRead(read); status.setMarkable(!read); } else { @@ -92,6 +98,12 @@ public class FeedEntryStatusDAO extends GenericDAO { return status; } + private FeedEntryStatus fetchTags(User user, FeedEntryStatus status) { + List tags = feedEntryTagDAO.findByEntry(user, status.getEntry()); + status.setTags(tags); + return status; + } + public List findStarred(User user, Date newerThan, int offset, int limit, ReadingOrder order, boolean includeContent) { CriteriaQuery query = builder.createQuery(getType()); @@ -114,13 +126,14 @@ public class FeedEntryStatusDAO extends GenericDAO { setTimeout(q); List statuses = q.getResultList(); for (FeedEntryStatus status : statuses) { - status = handleStatus(status, status.getSubscription(), status.getEntry()); + status = handleStatus(user, status, status.getSubscription(), status.getEntry()); + status = fetchTags(user, status); } return lazyLoadContent(includeContent, statuses); } private Criteria buildSearchCriteria(FeedSubscription sub, boolean unreadOnly, String keywords, Date newerThan, int offset, int limit, - ReadingOrder order, Date last) { + ReadingOrder order, Date last, String tag) { Criteria criteria = getSession().createCriteria(FeedEntry.class, ALIAS_ENTRY); criteria.add(Restrictions.eq(FeedEntry_.feed.getName(), sub.getFeed())); @@ -138,7 +151,7 @@ public class FeedEntryStatusDAO extends GenericDAO { Criteria statusJoin = criteria.createCriteria(FeedEntry_.statuses.getName(), ALIAS_STATUS, JoinType.LEFT_OUTER_JOIN, Restrictions.eq(FeedEntryStatus_.subscription.getName(), sub)); - if (unreadOnly) { + if (unreadOnly && tag == null) { Disjunction or = Restrictions.disjunction(); or.add(Restrictions.isNull(FeedEntryStatus_.read.getName())); @@ -151,6 +164,11 @@ public class FeedEntryStatusDAO extends GenericDAO { } } + if (tag != null) { + criteria.createCriteria(FeedEntry_.tags.getName(), ALIAS_TAG, JoinType.INNER_JOIN, + Restrictions.eq(FeedEntryTag_.name.getName(), tag)); + } + if (newerThan != null) { criteria.add(Restrictions.ge(FeedEntry_.inserted.getName(), newerThan)); } @@ -185,14 +203,14 @@ public class FeedEntryStatusDAO extends GenericDAO { } @SuppressWarnings("unchecked") - public List findBySubscriptions(List subs, boolean unreadOnly, String keywords, Date newerThan, - int offset, int limit, ReadingOrder order, boolean includeContent, boolean onlyIds) { + public List findBySubscriptions(User user, List subs, boolean unreadOnly, String keywords, + Date newerThan, int offset, int limit, ReadingOrder order, boolean includeContent, boolean onlyIds, String tag) { int capacity = offset + limit; Comparator comparator = order == ReadingOrder.desc ? STATUS_COMPARATOR_DESC : STATUS_COMPARATOR_ASC; FixedSizeSortedSet set = new FixedSizeSortedSet(capacity, comparator); for (FeedSubscription sub : subs) { Date last = (order != null && set.isFull()) ? set.last().getEntryUpdated() : null; - Criteria criteria = buildSearchCriteria(sub, unreadOnly, keywords, newerThan, -1, capacity, order, last); + Criteria criteria = buildSearchCriteria(sub, unreadOnly, keywords, newerThan, -1, capacity, order, last, tag); ProjectionList projection = Projections.projectionList(); projection.add(Projections.property("id"), "id"); projection.add(Projections.property("updated"), "updated"); @@ -234,7 +252,10 @@ public class FeedEntryStatusDAO extends GenericDAO { for (FeedEntryStatus placeholder : placeholders) { Long statusId = placeholder.getId(); FeedEntry entry = em.find(FeedEntry.class, placeholder.getEntry().getId()); - statuses.add(handleStatus(statusId == null ? null : findById(statusId), placeholder.getSubscription(), entry)); + FeedEntryStatus status = handleStatus(user, statusId == null ? null : findById(statusId), placeholder.getSubscription(), + entry); + status = fetchTags(user, status); + statuses.add(status); } statuses = lazyLoadContent(includeContent, statuses); } @@ -244,7 +265,7 @@ public class FeedEntryStatusDAO extends GenericDAO { @SuppressWarnings("unchecked") public UnreadCount getUnreadCount(FeedSubscription subscription) { UnreadCount uc = null; - Criteria criteria = buildSearchCriteria(subscription, true, null, null, -1, -1, null, null); + Criteria criteria = buildSearchCriteria(subscription, true, null, null, -1, -1, null, null, null); ProjectionList projection = Projections.projectionList(); projection.add(Projections.rowCount(), "count"); projection.add(Projections.max(FeedEntry_.updated.getName()), "updated"); diff --git a/src/main/java/com/commafeed/backend/dao/FeedEntryTagDAO.java b/src/main/java/com/commafeed/backend/dao/FeedEntryTagDAO.java new file mode 100644 index 00000000..82981656 --- /dev/null +++ b/src/main/java/com/commafeed/backend/dao/FeedEntryTagDAO.java @@ -0,0 +1,43 @@ +package com.commafeed.backend.dao; + +import java.util.List; + +import javax.ejb.Stateless; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; + +import com.commafeed.backend.model.FeedEntry; +import com.commafeed.backend.model.FeedEntryTag; +import com.commafeed.backend.model.FeedEntryTag_; +import com.commafeed.backend.model.FeedEntry_; +import com.commafeed.backend.model.User; +import com.commafeed.backend.model.User_; + +@Stateless +public class FeedEntryTagDAO extends GenericDAO { + + public List findByUser(User user) { + CriteriaQuery query = builder.createQuery(String.class); + Root root = query.from(getType()); + query.select(root.get(FeedEntryTag_.name)); + query.distinct(true); + + Predicate p1 = builder.equal(root.get(FeedEntryTag_.user).get(User_.id), user.getId()); + query.where(p1); + + return cache(em.createQuery(query)).getResultList(); + } + + public List findByEntry(User user, FeedEntry entry) { + CriteriaQuery query = builder.createQuery(getType()); + Root root = query.from(getType()); + + Predicate p1 = builder.equal(root.get(FeedEntryTag_.user).get(User_.id), user.getId()); + Predicate p2 = builder.equal(root.get(FeedEntryTag_.entry).get(FeedEntry_.id), entry.getId()); + + query.where(p1, p2); + + return cache(em.createQuery(query)).getResultList(); + } +} diff --git a/src/main/java/com/commafeed/backend/model/FeedEntry.java b/src/main/java/com/commafeed/backend/model/FeedEntry.java index b2f2972d..3d8532bf 100644 --- a/src/main/java/com/commafeed/backend/model/FeedEntry.java +++ b/src/main/java/com/commafeed/backend/model/FeedEntry.java @@ -55,5 +55,8 @@ public class FeedEntry extends AbstractModel { @OneToMany(mappedBy = "entry", cascade = CascadeType.REMOVE) private Set statuses; + + @OneToMany(mappedBy = "entry", cascade = CascadeType.REMOVE) + private Set tags; } diff --git a/src/main/java/com/commafeed/backend/model/FeedEntryStatus.java b/src/main/java/com/commafeed/backend/model/FeedEntryStatus.java index e2360fe9..e46841e6 100644 --- a/src/main/java/com/commafeed/backend/model/FeedEntryStatus.java +++ b/src/main/java/com/commafeed/backend/model/FeedEntryStatus.java @@ -1,6 +1,7 @@ package com.commafeed.backend.model; import java.util.Date; +import java.util.List; import javax.persistence.Cacheable; import javax.persistence.Column; @@ -42,6 +43,9 @@ public class FeedEntryStatus extends AbstractModel { @Transient private boolean markable; + + @Transient + private List tags; /** * Denormalization starts here diff --git a/src/main/java/com/commafeed/backend/model/FeedEntryTag.java b/src/main/java/com/commafeed/backend/model/FeedEntryTag.java new file mode 100644 index 00000000..76a12aed --- /dev/null +++ b/src/main/java/com/commafeed/backend/model/FeedEntryTag.java @@ -0,0 +1,46 @@ +package com.commafeed.backend.model; + +import javax.persistence.Cacheable; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import lombok.Getter; +import lombok.Setter; + +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; + +@Entity +@Table(name = "FEEDENTRYTAGS") +@SuppressWarnings("serial") +@Cacheable +@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL) +@Getter +@Setter +public class FeedEntryTag extends AbstractModel { + + @JoinColumn(name = "user_id") + @ManyToOne(fetch = FetchType.LAZY) + private User user; + + @JoinColumn(name = "entry_id") + @ManyToOne(fetch = FetchType.LAZY) + private FeedEntry entry; + + @Column(name = "name", length = 40) + private String name; + + public FeedEntryTag() { + } + + public FeedEntryTag(User user, FeedEntry entry, String name) { + this.name = name; + this.entry = entry; + this.user = user; + } + +} diff --git a/src/main/java/com/commafeed/backend/services/FeedEntryService.java b/src/main/java/com/commafeed/backend/services/FeedEntryService.java index b32de691..f8a5cb56 100644 --- a/src/main/java/com/commafeed/backend/services/FeedEntryService.java +++ b/src/main/java/com/commafeed/backend/services/FeedEntryService.java @@ -43,7 +43,7 @@ public class FeedEntryService { return; } - FeedEntryStatus status = feedEntryStatusDAO.getStatus(sub, entry); + FeedEntryStatus status = feedEntryStatusDAO.getStatus(user, sub, entry); if (status.isMarkable()) { status.setRead(read); feedEntryStatusDAO.saveOrUpdate(status); @@ -64,14 +64,14 @@ public class FeedEntryService { return; } - FeedEntryStatus status = feedEntryStatusDAO.getStatus(sub, entry); + FeedEntryStatus status = feedEntryStatusDAO.getStatus(user, sub, entry); status.setStarred(starred); feedEntryStatusDAO.saveOrUpdate(status); } public void markSubscriptionEntries(User user, List subscriptions, Date olderThan) { - List statuses = feedEntryStatusDAO - .findBySubscriptions(subscriptions, true, null, null, -1, -1, null, false, false); + List statuses = feedEntryStatusDAO.findBySubscriptions(user, subscriptions, true, null, null, -1, -1, null, false, + false, null); markList(statuses, olderThan); cache.invalidateUnreadCount(subscriptions.toArray(new FeedSubscription[0])); cache.invalidateUserRootCategory(user); diff --git a/src/main/java/com/commafeed/backend/services/FeedEntryTagService.java b/src/main/java/com/commafeed/backend/services/FeedEntryTagService.java new file mode 100644 index 00000000..9eccea24 --- /dev/null +++ b/src/main/java/com/commafeed/backend/services/FeedEntryTagService.java @@ -0,0 +1,61 @@ +package com.commafeed.backend.services; + +import java.util.List; +import java.util.Map; + +import javax.ejb.Stateless; +import javax.inject.Inject; + +import com.commafeed.backend.dao.FeedEntryDAO; +import com.commafeed.backend.dao.FeedEntryTagDAO; +import com.commafeed.backend.model.FeedEntry; +import com.commafeed.backend.model.FeedEntryTag; +import com.commafeed.backend.model.User; +import com.google.common.base.Function; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +@Stateless +public class FeedEntryTagService { + + @Inject + FeedEntryDAO feedEntryDAO; + + @Inject + FeedEntryTagDAO feedEntryTagDAO; + + public void updateTags(User user, Long entryId, List tagNames) { + FeedEntry entry = feedEntryDAO.findById(entryId); + if (entry == null) { + return; + } + + List tags = feedEntryTagDAO.findByEntry(user, entry); + Map tagMap = Maps.uniqueIndex(tags, new Function() { + @Override + public String apply(FeedEntryTag input) { + return input.getName(); + } + }); + + List addList = Lists.newArrayList(); + List removeList = Lists.newArrayList(); + + for (String tagName : tagNames) { + FeedEntryTag tag = tagMap.get(tagName); + if (tag == null) { + addList.add(new FeedEntryTag(user, entry, tagName)); + } + } + + for (FeedEntryTag tag : tags) { + if (!tagNames.contains(tag.getName())) { + removeList.add(tag); + } + } + + feedEntryTagDAO.saveOrUpdate(addList); + feedEntryTagDAO.delete(removeList); + } + +} diff --git a/src/main/java/com/commafeed/frontend/model/Entry.java b/src/main/java/com/commafeed/frontend/model/Entry.java index 72063eb3..bcbc9c74 100644 --- a/src/main/java/com/commafeed/frontend/model/Entry.java +++ b/src/main/java/com/commafeed/frontend/model/Entry.java @@ -3,6 +3,7 @@ package com.commafeed.frontend.model; import java.io.Serializable; import java.util.Arrays; import java.util.Date; +import java.util.List; import lombok.Data; @@ -10,7 +11,9 @@ import com.commafeed.backend.feeds.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.google.common.collect.Lists; import com.sun.syndication.feed.synd.SyndContentImpl; import com.sun.syndication.feed.synd.SyndEntry; import com.sun.syndication.feed.synd.SyndEntryImpl; @@ -43,6 +46,12 @@ public class Entry implements Serializable { entry.setFeedLink(sub.getFeed().getLink()); entry.setIconUrl(FeedUtils.getFaviconUrl(sub, publicUrl)); + List tags = Lists.newArrayList(); + for (FeedEntryTag tag : status.getTags()) { + tags.add(tag.getName()); + } + entry.setTags(tags); + if (content != null) { entry.setRtl(FeedUtils.isRTL(feedEntry)); entry.setTitle(content.getTitle()); @@ -125,4 +134,7 @@ public class Entry implements Serializable { @ApiProperty("wether the entry is still markable (old entry statuses are discarded)") private boolean markable; + + @ApiProperty("tags") + private List tags; } diff --git a/src/main/java/com/commafeed/frontend/model/request/TagRequest.java b/src/main/java/com/commafeed/frontend/model/request/TagRequest.java new file mode 100644 index 00000000..eb4b87f1 --- /dev/null +++ b/src/main/java/com/commafeed/frontend/model/request/TagRequest.java @@ -0,0 +1,22 @@ +package com.commafeed.frontend.model.request; + +import java.io.Serializable; +import java.util.List; + +import lombok.Data; + +import com.wordnik.swagger.annotations.ApiClass; +import com.wordnik.swagger.annotations.ApiProperty; + +@SuppressWarnings("serial") +@ApiClass("Tag Request") +@Data +public class TagRequest implements Serializable { + + @ApiProperty(value = "entry id", required = true) + private Long entryId; + + @ApiProperty(value = "tags") + private List tags; + +} diff --git a/src/main/java/com/commafeed/frontend/pages/NextUnreadRedirectPage.java b/src/main/java/com/commafeed/frontend/pages/NextUnreadRedirectPage.java index f205dc2b..571413ba 100755 --- a/src/main/java/com/commafeed/frontend/pages/NextUnreadRedirectPage.java +++ b/src/main/java/com/commafeed/frontend/pages/NextUnreadRedirectPage.java @@ -54,13 +54,13 @@ public class NextUnreadRedirectPage extends WebPage { List statuses = null; if (StringUtils.isBlank(categoryId) || CategoryREST.ALL.equals(categoryId)) { List subs = feedSubscriptionDAO.findAll(user); - statuses = feedEntryStatusDAO.findBySubscriptions(subs, true, null, null, 0, 1, order, true, false); + statuses = feedEntryStatusDAO.findBySubscriptions(user, subs, true, null, null, 0, 1, order, true, false, null); } else { FeedCategory category = feedCategoryDAO.findById(user, Long.valueOf(categoryId)); if (category != null) { List children = feedCategoryDAO.findAllChildrenCategories(user, category); List subscriptions = feedSubscriptionDAO.findByCategories(user, children); - statuses = feedEntryStatusDAO.findBySubscriptions(subscriptions, true, null, null, 0, 1, order, true, false); + statuses = feedEntryStatusDAO.findBySubscriptions(user, subscriptions, true, null, null, 0, 1, order, true, false, null); } } diff --git a/src/main/java/com/commafeed/frontend/rest/resources/CategoryREST.java b/src/main/java/com/commafeed/frontend/rest/resources/CategoryREST.java index 35948b1c..0b5622a9 100644 --- a/src/main/java/com/commafeed/frontend/rest/resources/CategoryREST.java +++ b/src/main/java/com/commafeed/frontend/rest/resources/CategoryREST.java @@ -106,7 +106,8 @@ public class CategoryREST extends AbstractREST { @ApiParam( value = "search for keywords in either the title or the content of the entries, separated by spaces, 3 characters minimum") @QueryParam("keywords") String keywords, @ApiParam(value = "return only entry ids") @DefaultValue("false") @QueryParam("onlyIds") boolean onlyIds, - @ApiParam(value = "comma-separated list of excluded subscription ids") @QueryParam("excludedSubscriptionIds") String excludedSubscriptionIds) { + @ApiParam(value = "comma-separated list of excluded subscription ids") @QueryParam("excludedSubscriptionIds") String excludedSubscriptionIds, + @ApiParam(value = "keep only entries tagged with this tag") @QueryParam("tag") String tag) { Preconditions.checkNotNull(readType); @@ -138,8 +139,8 @@ public class CategoryREST extends AbstractREST { entries.setName("All"); List subs = feedSubscriptionDAO.findAll(getUser()); removeExcludedSubscriptions(subs, excludedIds); - List list = feedEntryStatusDAO.findBySubscriptions(subs, unreadOnly, keywords, newerThanDate, offset, - limit + 1, order, true, onlyIds); + List list = feedEntryStatusDAO.findBySubscriptions(getUser(), subs, unreadOnly, keywords, newerThanDate, + offset, limit + 1, order, true, onlyIds, tag); for (FeedEntryStatus status : list) { entries.getEntries().add( @@ -161,8 +162,8 @@ public class CategoryREST extends AbstractREST { List categories = feedCategoryDAO.findAllChildrenCategories(getUser(), parent); List subs = feedSubscriptionDAO.findByCategories(getUser(), categories); removeExcludedSubscriptions(subs, excludedIds); - List list = feedEntryStatusDAO.findBySubscriptions(subs, unreadOnly, keywords, newerThanDate, offset, - limit + 1, order, true, onlyIds); + List list = feedEntryStatusDAO.findBySubscriptions(getUser(), subs, unreadOnly, keywords, newerThanDate, + offset, limit + 1, order, true, onlyIds, tag); for (FeedEntryStatus status : list) { entries.getEntries().add( @@ -192,7 +193,9 @@ public class CategoryREST extends AbstractREST { @Produces(MediaType.APPLICATION_XML) @SecurityCheck(value = Role.USER, apiKeyAllowed = true) public Response getCategoryEntriesAsFeed( - @ApiParam(value = "id of the category, 'all' or 'starred'", required = true) @QueryParam("id") String id) { + @ApiParam(value = "id of the category, 'all' or 'starred'", required = true) @QueryParam("id") String id, @ApiParam( + value = "tag", + required = true) @QueryParam("tag") String tag) { Preconditions.checkNotNull(id); @@ -201,7 +204,7 @@ public class CategoryREST extends AbstractREST { int offset = 0; int limit = 20; - Response response = getCategoryEntries(id, readType, null, offset, limit, order, null, false, null); + Response response = getCategoryEntries(id, readType, null, offset, limit, order, null, false, null, tag); if (response.getStatus() != Status.OK.getStatusCode()) { return response; } diff --git a/src/main/java/com/commafeed/frontend/rest/resources/EntryREST.java b/src/main/java/com/commafeed/frontend/rest/resources/EntryREST.java index 09bed141..0669e6f0 100644 --- a/src/main/java/com/commafeed/frontend/rest/resources/EntryREST.java +++ b/src/main/java/com/commafeed/frontend/rest/resources/EntryREST.java @@ -1,17 +1,23 @@ package com.commafeed.frontend.rest.resources; +import java.util.List; + import javax.inject.Inject; +import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.core.Response; import com.commafeed.backend.dao.FeedEntryStatusDAO; +import com.commafeed.backend.dao.FeedEntryTagDAO; import com.commafeed.backend.dao.FeedSubscriptionDAO; import com.commafeed.backend.services.ApplicationSettingsService; import com.commafeed.backend.services.FeedEntryService; +import com.commafeed.backend.services.FeedEntryTagService; import com.commafeed.frontend.model.request.MarkRequest; import com.commafeed.frontend.model.request.MultipleMarkRequest; import com.commafeed.frontend.model.request.StarRequest; +import com.commafeed.frontend.model.request.TagRequest; import com.google.common.base.Preconditions; import com.wordnik.swagger.annotations.Api; import com.wordnik.swagger.annotations.ApiOperation; @@ -30,6 +36,12 @@ public class EntryREST extends AbstractREST { @Inject FeedSubscriptionDAO feedSubscriptionDAO; + @Inject + FeedEntryTagDAO feedEntryTagDAO; + + @Inject + FeedEntryTagService feedEntryTagService; + @Inject ApplicationSettingsService applicationSettingsService; @@ -71,4 +83,24 @@ public class EntryREST extends AbstractREST { return Response.ok().build(); } + @Path("/tags") + @GET + @ApiOperation(value = "Get list of tags for the user", notes = "Get list of tags for the user") + public Response getTags() { + List tags = feedEntryTagDAO.findByUser(getUser()); + return Response.ok(tags).build(); + } + + @Path("/tag") + @POST + @ApiOperation(value = "Mark a feed entry", notes = "Mark a feed entry as read/unread") + public Response tagFeedEntry(@ApiParam(value = "Tag Request", required = true) TagRequest req) { + Preconditions.checkNotNull(req); + Preconditions.checkNotNull(req.getEntryId()); + + feedEntryTagService.updateTags(getUser(), req.getEntryId(), req.getTags()); + + return Response.ok().build(); + } + } diff --git a/src/main/java/com/commafeed/frontend/rest/resources/FeedREST.java b/src/main/java/com/commafeed/frontend/rest/resources/FeedREST.java index 97638ab4..2b525e6e 100644 --- a/src/main/java/com/commafeed/frontend/rest/resources/FeedREST.java +++ b/src/main/java/com/commafeed/frontend/rest/resources/FeedREST.java @@ -167,8 +167,8 @@ public class FeedREST extends AbstractREST { entries.setErrorCount(subscription.getFeed().getErrorCount()); entries.setFeedLink(subscription.getFeed().getLink()); - List list = feedEntryStatusDAO.findBySubscriptions(Arrays.asList(subscription), unreadOnly, keywords, - newerThanDate, offset, limit + 1, order, true, onlyIds); + List list = feedEntryStatusDAO.findBySubscriptions(getUser(), Arrays.asList(subscription), unreadOnly, + keywords, newerThanDate, offset, limit + 1, order, true, onlyIds, null); for (FeedEntryStatus status : list) { entries.getEntries().add( diff --git a/src/main/resources/changelogs/db.changelog-1.4.xml b/src/main/resources/changelogs/db.changelog-1.4.xml index 6a0d773c..9f358059 100644 --- a/src/main/resources/changelogs/db.changelog-1.4.xml +++ b/src/main/resources/changelogs/db.changelog-1.4.xml @@ -9,8 +9,39 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/i18n/ar.properties b/src/main/resources/i18n/ar.properties index 135db3f2..9088ff83 100644 --- a/src/main/resources/i18n/ar.properties +++ b/src/main/resources/i18n/ar.properties @@ -6,6 +6,7 @@ global.download=تحميل global.link=رابط global.bookmark=مرجعية global.close=أغلق +global.tags=Tags ####### Needs translation tree.subscribe=اشترك tree.import=استورد @@ -67,6 +68,8 @@ settings.general.show_unread=Show feeds and categories with no unread entries settings.general.social_buttons=Show social sharing buttons settings.general.scroll_marks=In expanded view, scrolling through entries mark them as read settings.appearance=Appearance +settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation +settings.scroll_speed.help=set to 0 to disable ####### Needs translation settings.theme=Theme settings.submit_your_theme=Submit your theme settings.custom_css=Custom CSS @@ -85,6 +88,7 @@ details.feed_url=Feed URL details.generate_api_key_first=Generate an API key in your profile first. details.unsubscribe=Unsubscribe details.category_details=Category details +details.tag_details=Tag details ####### Needs translation details.parent_category=Parent category profile.user_name=User name diff --git a/src/main/resources/i18n/cs.properties b/src/main/resources/i18n/cs.properties index b52af9a5..9fa111f3 100644 --- a/src/main/resources/i18n/cs.properties +++ b/src/main/resources/i18n/cs.properties @@ -6,6 +6,7 @@ global.download = Stáhnout global.link = Odkaz global.bookmark = Záložky global.close = Zavřít +global.tags=Tags ####### Needs translation tree.subscribe = Nový odběr tree.import = Importovat @@ -67,6 +68,8 @@ settings.general.show_unread = Zobrazit položky a kategorie z přečtenými pol settings.general.social_buttons = Zobrazit možnosti sdílení settings.general.scroll_marks = Skrolování v rozšířeném náhledu označí položky jako přečtené settings.appearance = Vzhled +settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation +settings.scroll_speed.help=set to 0 to disable ####### Needs translation settings.theme = Motiv settings.submit_your_theme = Nahrát vlastní motiv settings.custom_css = Vlastní motiv (CSS) @@ -85,6 +88,7 @@ details.feed_url = URL RSS zdroje details.generate_api_key_first = Vygenerujte si API klíč na stránce vašeho profilu. details.unsubscribe = Odhlásit odběr. details.category_details = Detail kategorie +details.tag_details=Tag details ####### Needs translation details.parent_category = Hlavní kategorie profile.user_name = Uživatelské jméno diff --git a/src/main/resources/i18n/cy.properties b/src/main/resources/i18n/cy.properties index 1c664552..3d5d4741 100644 --- a/src/main/resources/i18n/cy.properties +++ b/src/main/resources/i18n/cy.properties @@ -6,6 +6,7 @@ global.download=Lawrlwytho global.link=Dolen global.bookmark=Nod tudalen global.close=Cau +global.tags=Tags ####### Needs translation tree.subscribe=Tanysgrifio tree.import=Mewnforio @@ -67,6 +68,8 @@ settings.general.show_unread=Dangos ffrydiau a chategoriau gyda dim eitemau heb settings.general.social_buttons=Dangos botymau rhannu settings.general.scroll_marks=Marcio eitemau fel wedi eu darllen wrth sgrolio drwyddynt yn y golwg estynedig ###### Defnyddio gystrawen debyg i'r ddau uwch. settings.appearance=Golwg +settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation +settings.scroll_speed.help=set to 0 to disable ####### Needs translation settings.theme=Thema settings.submit_your_theme=Cyflwyna dy thema settings.custom_css=CSS wedi'i addasu @@ -85,6 +88,7 @@ details.feed_url=URL Ffrwd details.generate_api_key_first=Rhaid creu allwedd API yn dy broffil yn gyntaf. details.unsubscribe=Dad-danysgrifio details.category_details=Manylion categori +details.tag_details=Tag details ####### Needs translation details.parent_category=Categori rhiant profile.user_name=Enw defnyddiwr diff --git a/src/main/resources/i18n/da.properties b/src/main/resources/i18n/da.properties index a50c5d33..11c3aa94 100644 --- a/src/main/resources/i18n/da.properties +++ b/src/main/resources/i18n/da.properties @@ -6,6 +6,7 @@ global.download=Hent global.link=Link global.bookmark=Bogmærke global.close=Luk +global.tags=Tags ####### Needs translation tree.subscribe=Abonner tree.import=Importer @@ -67,6 +68,8 @@ settings.general.show_unread=Vis abonnomenter og kategorier med læste artikler settings.general.social_buttons=Vis delingsknapper settings.general.scroll_marks=I udvidet visning, marker artikler som læste når der rulles forbi dem settings.appearance=Udseende +settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation +settings.scroll_speed.help=set to 0 to disable ####### Needs translation settings.theme=Tema settings.submit_your_theme=Indsend dit tema settings.custom_css=Brugerdefineret CSS @@ -85,6 +88,7 @@ details.feed_url=URL for abonnement details.generate_api_key_first=Generer en API nøgle i din profil først. details.unsubscribe=Afmeld abonnement details.category_details=Kategori detaljer +details.tag_details=Tag details ####### Needs translation details.parent_category=Overordnet kategori profile.user_name=Brugernavn diff --git a/src/main/resources/i18n/de.properties b/src/main/resources/i18n/de.properties index b183c8ba..848cf9cc 100644 --- a/src/main/resources/i18n/de.properties +++ b/src/main/resources/i18n/de.properties @@ -6,6 +6,7 @@ global.download=Herunterladen global.link=Link global.bookmark=Lesezeichen global.close=Schließen +global.tags=Tags ####### Needs translation tree.subscribe=Abonnieren tree.import=Importieren @@ -67,6 +68,8 @@ settings.general.show_unread=Zeige Feeds und Kategorien mit ungelesenen Einträg settings.general.social_buttons=Zeige Buttons zum Teilen von Inhalten über soziale Netzwerke settings.general.scroll_marks=In der ausgedehnten Ansicht werden Artikel beim Scrollen als gelesen markiert settings.appearance=Aussehen +settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation +settings.scroll_speed.help=set to 0 to disable ####### Needs translation settings.theme=Theme settings.submit_your_theme=Füg dein Theme hinzu settings.custom_css=Eigenes CSS @@ -85,6 +88,7 @@ details.feed_url=Feed Adresse details.generate_api_key_first=Generiere zuerst einen API Schlüssel in deinem Profil. details.unsubscribe=Kündigen details.category_details=Kategoriedetails +details.tag_details=Tag details ####### Needs translation details.parent_category=Übergeordnete Kategorie profile.user_name=Benutzername diff --git a/src/main/resources/i18n/en.properties b/src/main/resources/i18n/en.properties index 9dc6b0ea..1954e53b 100644 --- a/src/main/resources/i18n/en.properties +++ b/src/main/resources/i18n/en.properties @@ -6,6 +6,7 @@ global.download=Download global.link=Link global.bookmark=Bookmark global.close=Close +global.tags=Tags tree.subscribe=Subscribe tree.import=Import @@ -87,6 +88,7 @@ details.feed_url=Feed URL details.generate_api_key_first=Generate an API key in your profile first. details.unsubscribe=Unsubscribe details.category_details=Category details +details.tag_details=Tag details details.parent_category=Parent category profile.user_name=User name diff --git a/src/main/resources/i18n/es.properties b/src/main/resources/i18n/es.properties index e27742c7..6952e670 100644 --- a/src/main/resources/i18n/es.properties +++ b/src/main/resources/i18n/es.properties @@ -6,6 +6,7 @@ global.download=Descargar global.link=Enlace global.bookmark=Marcador global.close=Close ####### Needs translation +global.tags=Tags ####### Needs translation tree.subscribe=Subscribir tree.import=Importar @@ -67,6 +68,8 @@ settings.general.show_unread=Mostrar canales y categorías sin entradas no leíd settings.general.social_buttons=Mostrar botones de compartir de redes sociales. settings.general.scroll_marks=En vista expandida, el desplazamiento por las entradas las marca como leídas settings.appearance=Appearance ####### Needs translation +settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation +settings.scroll_speed.help=set to 0 to disable ####### Needs translation settings.theme=Theme ####### Needs translation settings.submit_your_theme=Submit your theme ####### Needs translation settings.custom_css=CSS Personalizado @@ -85,6 +88,7 @@ details.feed_url=URL del Canal details.generate_api_key_first=Genera una llave API en tu perfil primero. details.unsubscribe=Terminar subscripción details.category_details=Detalles de la categoría +details.tag_details=Tag details ####### Needs translation details.parent_category=Categoría principal profile.user_name=Nombre de usuario diff --git a/src/main/resources/i18n/fa.properties b/src/main/resources/i18n/fa.properties index d76fda5c..e9927d0c 100644 --- a/src/main/resources/i18n/fa.properties +++ b/src/main/resources/i18n/fa.properties @@ -6,6 +6,7 @@ global.download=بارگیری global.link=پیوند global.bookmark=بوکمارک global.close=بستن +global.tags=Tags ####### Needs translation tree.subscribe=مشترک شوید tree.import=درون‌ریزی @@ -67,6 +68,8 @@ settings.general.show_unread=تنها خوراک‌ها و دسته‌های ر settings.general.social_buttons=نشان‌دادن دکمه‌های اشتراک‌گذاری در شبکه‌های اجتماعی settings.general.scroll_marks=در نمای گسترش‌یافته، لغزیدن بر روی مطالب به‌عنوان نشانه‌گذاری به‌عنوان خوانده‌شده در نظر گرفته‌شوند. settings.appearance=ظاهر +settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation +settings.scroll_speed.help=set to 0 to disable ####### Needs translation settings.theme=پوسته settings.submit_your_theme=پوستهٔ خود را ارسال‌کنید settings.custom_css=سی‌اس‌اس شخصی‌سازی‌شده @@ -85,6 +88,7 @@ details.feed_url=نشانی خوراک details.generate_api_key_first=ابتدا یک کلید API در نمایهٔ خود ایجاد کنید. details.unsubscribe=لغو اشتراک details.category_details=جزئیات دسته +details.tag_details=Tag details ####### Needs translation details.parent_category=ردهٔ پدر profile.user_name=نام کاربری diff --git a/src/main/resources/i18n/fi.properties b/src/main/resources/i18n/fi.properties index 67a2cbdc..ea25f3ce 100644 --- a/src/main/resources/i18n/fi.properties +++ b/src/main/resources/i18n/fi.properties @@ -6,6 +6,7 @@ global.download=Lataa global.link=Linkki global.bookmark=Kirjanmerkki global.close=Sulje +global.tags=Tags ####### Needs translation tree.subscribe=Tilaa syöte tree.import=Tuo @@ -67,6 +68,8 @@ settings.general.show_unread=Näytä syötteet ja kansiot, joissa ei ole lukemat settings.general.social_buttons=Näytä jakonapit settings.general.scroll_marks=Laajennetussa näkymässä otsikoiden selaaminen merkitsee ne luetuiksi settings.appearance=Ulkonäkö +settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation +settings.scroll_speed.help=set to 0 to disable ####### Needs translation settings.theme=Teema settings.submit_your_theme=Lähetä oma teemasi settings.custom_css=Oma CSS @@ -85,6 +88,7 @@ details.feed_url=Syötteen osoite details.generate_api_key_first=Luo API-avain profiilissasi. details.unsubscribe=Peruuta tilaus details.category_details=Kansion tiedot +details.tag_details=Tag details ####### Needs translation details.parent_category=Yläkansio profile.user_name=Käyttäjänimi diff --git a/src/main/resources/i18n/fr.properties b/src/main/resources/i18n/fr.properties index 36d14c31..7c0a3f2b 100644 --- a/src/main/resources/i18n/fr.properties +++ b/src/main/resources/i18n/fr.properties @@ -6,6 +6,7 @@ global.download=Télécharger global.link=Lien global.bookmark=Favoris global.close=Fermer +global.tags=Tags ####### Needs translation tree.subscribe=S'abonner tree.import=Importer @@ -67,6 +68,8 @@ settings.general.show_unread=Afficher les flux et les catégories pour lesquels settings.general.social_buttons=Afficher les boutons de partage sur réseaux sociaux settings.general.scroll_marks=En mode de lecture étendu, marquer comme lu les éléments lorsque la fenêtre descend. settings.appearance=Apparence +settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation +settings.scroll_speed.help=set to 0 to disable ####### Needs translation settings.theme=Thème settings.submit_your_theme=Soumettez votre thème. settings.custom_css=CSS personnelle @@ -85,6 +88,7 @@ details.feed_url=URL du flux details.generate_api_key_first=Générez une clé API dans votre profil d'abord. details.unsubscribe=Se désabonner details.category_details=Détails de la catégorie +details.tag_details=Tag details ####### Needs translation details.parent_category=Catégorie parente profile.user_name=Nom diff --git a/src/main/resources/i18n/gl.properties b/src/main/resources/i18n/gl.properties index 88bc0f09..032a6aca 100644 --- a/src/main/resources/i18n/gl.properties +++ b/src/main/resources/i18n/gl.properties @@ -6,6 +6,7 @@ global.download=Descargar global.link=Ligazón global.bookmark=Marcador global.close=Pechar +global.tags=Tags ####### Needs translation tree.subscribe=Subscribir tree.import=Importar @@ -67,6 +68,8 @@ settings.general.show_unread=Mostrar fontes e categorías sen entradas non lidas settings.general.social_buttons=Mostrar botóns de compartir en redes sociais. settings.general.scroll_marks=En vista expandida, o desplazamento polas entradas márcaas como lidas. settings.appearance=Aspecto +settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation +settings.scroll_speed.help=set to 0 to disable ####### Needs translation settings.theme=Decorado settings.submit_your_theme=Envíe o seu decorado settings.custom_css=CSS Personalizado @@ -85,6 +88,7 @@ details.feed_url=URL da fonte details.generate_api_key_first=Antes debes xerar unha chave API no teu perfil. details.unsubscribe=Rematar suscripción details.category_details=Detalles da categoría +details.tag_details=Tag details ####### Needs translation details.parent_category=Categoría principal profile.user_name=Nome de usuario diff --git a/src/main/resources/i18n/glk.properties b/src/main/resources/i18n/glk.properties index 04dc0230..3189f5e5 100644 --- a/src/main/resources/i18n/glk.properties +++ b/src/main/resources/i18n/glk.properties @@ -6,6 +6,7 @@ global.download=جیرأکش global.link=خال global.bookmark=بوکمارک global.close=دَوَستن +global.tags=Tags ####### Needs translation tree.subscribe=مشترک ببید tree.import=درینأدأن @@ -67,6 +68,8 @@ settings.general.show_unread=تنها خوراک‌ها و دسته‌های ر settings.general.social_buttons=نشان‌دادن دکمه‌های اشتراک‌گذاری در شبکه‌های اجتماعی settings.general.scroll_marks=در نمای گسترش‌یافته، لغزیدن بر روی مطالب به‌عنوان نشانه‌گذاری به‌عنوان خوانده‌شده در نظر گرفته‌شوند. settings.appearance=ظاهر +settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation +settings.scroll_speed.help=set to 0 to disable ####### Needs translation settings.theme=پوسته settings.submit_your_theme=شیمه پوستهٰ اوسه کونید settings.custom_css=سی‌اس‌اس شخصی‌سازی‌شده @@ -85,6 +88,7 @@ details.feed_url=نشانی خوراک details.generate_api_key_first=ابتدا یک کلید API در نمایهٔ خود ایجاد کنید. details.unsubscribe=لغو اشتراک details.category_details=جرگه جزئیات +details.tag_details=Tag details ####### Needs translation details.parent_category=پئرˇ جرگه profile.user_name=کاربری نام diff --git a/src/main/resources/i18n/hu.properties b/src/main/resources/i18n/hu.properties index add8da00..955ced51 100644 --- a/src/main/resources/i18n/hu.properties +++ b/src/main/resources/i18n/hu.properties @@ -6,6 +6,7 @@ global.download=Letöltés global.link=Link global.bookmark=Könyvjelző global.close=Bezár +global.tags=Tags ####### Needs translation tree.subscribe=Feliratkozás tree.import=Importálás @@ -67,6 +68,8 @@ settings.general.show_unread=Mutassa azokat a hírcsatornákat és kategóriáka settings.general.social_buttons=Mutassa a közösségi oldalak megosztás gombjait settings.general.scroll_marks=Kiterjesztett nézetben, görgetéssel olvasottként jelöli meg a bejegyzést settings.appearance=Megjelenés +settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation +settings.scroll_speed.help=set to 0 to disable ####### Needs translation settings.theme=Téma settings.submit_your_theme=Küldje el a témáját settings.custom_css=Saját CSS @@ -85,6 +88,7 @@ details.feed_url=Hírcsatorna URL details.generate_api_key_first=A profiljában először egy API kulcsot kell generálnia. details.unsubscribe=Leiratkozás details.category_details=Kategória részletei +details.tag_details=Tag details ####### Needs translation details.parent_category=Szülő kategória profile.user_name=Felhasználói név diff --git a/src/main/resources/i18n/it.properties b/src/main/resources/i18n/it.properties index 97a375d8..7b9dbbbc 100644 --- a/src/main/resources/i18n/it.properties +++ b/src/main/resources/i18n/it.properties @@ -6,6 +6,7 @@ global.download=Download global.link=Link global.bookmark=Segnalibro global.close=Chiudi +global.tags=Tags ####### Needs translation tree.subscribe=Iscriviti tree.import=Importa @@ -67,6 +68,8 @@ settings.general.show_unread=Show feeds and categories with no unread entries settings.general.social_buttons=Visualizza i social button settings.general.scroll_marks=Marca come letto quando scorri settings.appearance=Appearance ####### Needs translation +settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation +settings.scroll_speed.help=set to 0 to disable ####### Needs translation settings.theme=Tema settings.submit_your_theme=Sottoponi il tuo tema settings.custom_css=Css modificato @@ -85,6 +88,7 @@ details.feed_url=Feed URL details.generate_api_key_first=Generate an API key in your profile first. details.unsubscribe=Annulla l"'"iscrizione details.category_details=Dettagli categoria +details.tag_details=Tag details ####### Needs translation details.parent_category=Parent category profile.user_name=User name diff --git a/src/main/resources/i18n/ko.properties b/src/main/resources/i18n/ko.properties index 375dfb23..11ed10d6 100644 --- a/src/main/resources/i18n/ko.properties +++ b/src/main/resources/i18n/ko.properties @@ -6,6 +6,7 @@ global.download=Download ####### Needs translation global.link=Link ####### Needs translation global.bookmark=Bookmark ####### Needs translation global.close=Close ####### Needs translation +global.tags=Tags ####### Needs translation tree.subscribe=구독 tree.import=임포트 @@ -67,6 +68,8 @@ settings.general.show_unread=안읽은 항목들이 있는 피드와 카테고 settings.general.social_buttons=소셜미디아 버튼들 보여주기 settings.general.scroll_marks=Expanded View에서 스크롤하면 항목들을 읽음으로 저장하기 settings.appearance=Appearance ####### Needs translation +settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation +settings.scroll_speed.help=set to 0 to disable ####### Needs translation settings.theme=Theme ####### Needs translation settings.submit_your_theme=Submit your theme ####### Needs translation settings.custom_css=커스톰 CSS @@ -85,6 +88,7 @@ details.feed_url=피드 유알엘 details.generate_api_key_first=당신의 프로필을 위해 API Key를 먼저 생성하세요. details.unsubscribe=주소 삭제 details.category_details=카테고리 세부 +details.tag_details=Tag details ####### Needs translation details.parent_category=부모 카테고리 profile.user_name=사용자 이름 diff --git a/src/main/resources/i18n/ms.properties b/src/main/resources/i18n/ms.properties index 8ae33772..c290cfed 100644 --- a/src/main/resources/i18n/ms.properties +++ b/src/main/resources/i18n/ms.properties @@ -6,6 +6,7 @@ global.download=Muat turun global.link=Pautan global.bookmark=Bookmark global.close=Tutup +global.tags=Tags ####### Needs translation tree.subscribe=Langgan tree.import=Import @@ -67,6 +68,8 @@ settings.general.show_unread=Tunjuk semua feed dan kategori yang telah dibaca settings.general.social_buttons=Tunjuk social sharing settings.general.scroll_marks=Dalam wide view, tanda item dibaca ketika scrolling settings.appearance=Rupa +settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation +settings.scroll_speed.help=set to 0 to disable ####### Needs translation settings.theme=Tema settings.submit_your_theme=Muat naik tema anda settings.custom_css=Custom CSS @@ -85,6 +88,7 @@ details.feed_url=URL Feed details.generate_api_key_first=Janakan API key dalam profil anda dahulu. details.unsubscribe=Hentikan langganan details.category_details=Butir-butir kategori +details.tag_details=Tag details ####### Needs translation details.parent_category=Kategori induk profile.user_name=User name diff --git a/src/main/resources/i18n/nb.properties b/src/main/resources/i18n/nb.properties index 6b5dfc43..f663704c 100644 --- a/src/main/resources/i18n/nb.properties +++ b/src/main/resources/i18n/nb.properties @@ -6,6 +6,7 @@ global.download=Last ned global.link=Lenke global.bookmark=Bokmerke global.close=Lukk +global.tags=Tags ####### Needs translation tree.subscribe=Abonner tree.import=Importer @@ -67,6 +68,8 @@ settings.general.show_unread=Vis abonnementer og kategorier uten nye artikler settings.general.social_buttons=Vis delingsknapper settings.general.scroll_marks=I utvidet visning, merk artikler som leste når du blar deg forbi dem. settings.appearance=Utseende +settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation +settings.scroll_speed.help=set to 0 to disable ####### Needs translation settings.theme=Drakt settings.submit_your_theme=Legg til egen drakt settings.custom_css=Egendefinert CSS @@ -85,6 +88,7 @@ details.feed_url=URL for abonnement details.generate_api_key_first=Generer en API-nøkkel under profilinnstillinger først. details.unsubscribe=Avslutt abonnement details.category_details=Kategoridetaljer +details.tag_details=Tag details ####### Needs translation details.parent_category=Overordnet kategori profile.user_name=Brukernavn diff --git a/src/main/resources/i18n/nl.properties b/src/main/resources/i18n/nl.properties index 83d26566..9a1e02a7 100644 --- a/src/main/resources/i18n/nl.properties +++ b/src/main/resources/i18n/nl.properties @@ -6,6 +6,7 @@ global.download=Download global.link=Link global.bookmark=Bookmark global.close=Sluiten +global.tags=Tags ####### Needs translation tree.subscribe=Abonneer tree.import=Importeer @@ -67,6 +68,8 @@ settings.general.show_unread=Laat feeds en categorieën zonder ongelezen artikel settings.general.social_buttons=Laat Social Media knoppen zien settings.general.scroll_marks=Markeer artikelen als gelezen, wanneer je er doorheen scrollt settings.appearance=Uiterlijk +settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation +settings.scroll_speed.help=set to 0 to disable ####### Needs translation settings.theme=Thema settings.submit_your_theme=Stuur thema in settings.custom_css=Custom CSS @@ -85,6 +88,7 @@ details.feed_url=Feed URL details.generate_api_key_first=Genereer eerst een API sleutel in je profiel. details.unsubscribe=Abonnement opzeggen details.category_details=Categorie details +details.tag_details=Tag details ####### Needs translation details.parent_category=Bovenliggende categorie profile.user_name=Gebruikersnaam diff --git a/src/main/resources/i18n/nn.properties b/src/main/resources/i18n/nn.properties index ea814b21..09f653d3 100644 --- a/src/main/resources/i18n/nn.properties +++ b/src/main/resources/i18n/nn.properties @@ -6,6 +6,7 @@ global.download=Last ned global.link=Lenkje global.bookmark=Bokmerke global.close=Lukk +global.tags=Tags ####### Needs translation tree.subscribe=Abonner tree.import=Importer @@ -67,6 +68,8 @@ settings.general.show_unread=Vis abonnement og kategoriar utan nye artiklar settings.general.social_buttons=Vis delingsknappar settings.general.scroll_marks=I utvida visning, merk artiklar som lesne når du blar deg forbi dei. settings.appearance=Utsjånad +settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation +settings.scroll_speed.help=set to 0 to disable ####### Needs translation settings.theme=Drakt settings.submit_your_theme=Legg til eiga drakt settings.custom_css=Skreddarsydd CSS @@ -85,6 +88,7 @@ details.feed_url=URL for abonnement details.generate_api_key_first=Generer ein API-nykel under profilinnstillingar fyrst. details.unsubscribe=Avslutt abonnement details.category_details=Kategoridetaljar +details.tag_details=Tag details ####### Needs translation details.parent_category=Overordna kategori profile.user_name=Brukarnamn diff --git a/src/main/resources/i18n/pl.properties b/src/main/resources/i18n/pl.properties index 89ad49fa..f9fe3e31 100644 --- a/src/main/resources/i18n/pl.properties +++ b/src/main/resources/i18n/pl.properties @@ -6,6 +6,7 @@ global.download=Pobierz global.link=Odnośnik global.bookmark=Zakładka global.close=Zamknij +global.tags=Tags ####### Needs translation tree.subscribe=Subskrybuj tree.import=Importuj @@ -67,6 +68,8 @@ settings.general.show_unread=Pokaż kanały i kategorie bez nieprzeczytanych ele settings.general.social_buttons=Pokaż przyciski udostępniania settings.general.scroll_marks=W widoku rozwiniętym przewijanie oznacza elementy jako przeczytane settings.appearance=Wygląd +settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation +settings.scroll_speed.help=set to 0 to disable ####### Needs translation settings.theme=Motyw settings.submit_your_theme=Wyślij swój motyw settings.custom_css=Własny styl CSS @@ -85,6 +88,7 @@ details.feed_url=URL kanału details.generate_api_key_first=Najpierw wygeneruj klucz API w swoim profilu. details.unsubscribe=Cofnij subskrypcje details.category_details=Szczegóły kategorii +details.tag_details=Tag details ####### Needs translation details.parent_category=Kategoria nadrzędna profile.user_name=Nazwa użytkownika diff --git a/src/main/resources/i18n/pt.properties b/src/main/resources/i18n/pt.properties index ab1fdbfc..44fbb521 100644 --- a/src/main/resources/i18n/pt.properties +++ b/src/main/resources/i18n/pt.properties @@ -6,6 +6,7 @@ global.download=Download global.link=Link global.bookmark=Favorito global.close=Fechar +global.tags=Tags ####### Needs translation tree.subscribe=Inscrever-se tree.import=Importar @@ -67,6 +68,8 @@ settings.general.show_unread=Mostrar feeds e categorias sem itens não lidos settings.general.social_buttons=Mostrar botões de mídias sociais settings.general.scroll_marks=No modo expandido, percorrer os itens marca-os como lidos settings.appearance=Aparência +settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation +settings.scroll_speed.help=set to 0 to disable ####### Needs translation settings.theme=Tema settings.submit_your_theme=Envie seu tema settings.custom_css=CSS personalizado @@ -85,6 +88,7 @@ details.feed_url=URL do feed details.generate_api_key_first=Gerar uma chave de API em seu perfil primeiro. details.unsubscribe=Cancelar inscrição details.category_details=Detalhes da categoria +details.tag_details=Tag details ####### Needs translation details.parent_category=Categoria pai profile.user_name=Nome de usuário diff --git a/src/main/resources/i18n/ru.properties b/src/main/resources/i18n/ru.properties index 79950a84..4c0c9459 100644 --- a/src/main/resources/i18n/ru.properties +++ b/src/main/resources/i18n/ru.properties @@ -6,6 +6,7 @@ global.download=Скачать global.link=Ссылка global.bookmark=Закладка global.close=Закрыть +global.tags=Tags ####### Needs translation tree.subscribe=Подписаться tree.import=Импорт @@ -67,6 +68,8 @@ settings.general.show_unread=Показывать прочтённые лент settings.general.social_buttons=Показывать социальные кнопки settings.general.scroll_marks=В развёрнутом виде помечать записи как прочитанные по мере прокрутки settings.appearance=Вид +settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation +settings.scroll_speed.help=set to 0 to disable ####### Needs translation settings.theme=Тема settings.submit_your_theme=Добавьте свою тему settings.custom_css=Собственная CSS @@ -85,6 +88,7 @@ details.feed_url=Адрес ленты details.generate_api_key_first=Сначала сгенерируйте API-ключ в вашем профиле. details.unsubscribe=Отписаться details.category_details=Информация о категории +details.tag_details=Tag details ####### Needs translation details.parent_category=Родительская категория profile.user_name=Имя пользователя diff --git a/src/main/resources/i18n/sk.properties b/src/main/resources/i18n/sk.properties index 30a8e13b..1c8d7875 100644 --- a/src/main/resources/i18n/sk.properties +++ b/src/main/resources/i18n/sk.properties @@ -6,6 +6,7 @@ global.download=Stiahnuť global.link=Link global.bookmark=Záložky global.close=Zavrieť +global.tags=Tags ####### Needs translation tree.subscribe=Odoberať tree.import=Importovať @@ -67,6 +68,8 @@ settings.general.show_unread=Zobraziť príspevky a kategórie bez neprečítan settings.general.social_buttons=Zobraziť možnosti zdieľania settings.general.scroll_marks=Scrollovanie v rozšírenom náhľade označí položky ako prečítané settings.appearance=Vzhľad +settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation +settings.scroll_speed.help=set to 0 to disable ####### Needs translation settings.theme=Motív settings.submit_your_theme=Nahrať vlastný motív vzhľadu settings.custom_css=Vlastný motív vzhľadu (CSS) @@ -85,6 +88,7 @@ details.feed_url=URL RSS zdroja details.generate_api_key_first=Vygenerujte si API kľúč vo vašom profile. details.unsubscribe=Zrušiť odoberanie. details.category_details=Detaily kategórie +details.tag_details=Tag details ####### Needs translation details.parent_category=Hlavná kategória profile.user_name=Uživateľské meno diff --git a/src/main/resources/i18n/sv.properties b/src/main/resources/i18n/sv.properties index 317a6bbd..a23dd94b 100644 --- a/src/main/resources/i18n/sv.properties +++ b/src/main/resources/i18n/sv.properties @@ -1,150 +1,154 @@ -global.save=Spara -global.cancel=Avbryt -global.delete=Radera -global.required=Obligatorisk -global.download=Ladda ned -global.link=Länka -global.bookmark=Bokmärk -global.close=Stäng - -tree.subscribe=Prenumerera -tree.import=Importera -tree.new_category=Ny kategori -tree.all=Alla -tree.starred=Stjärnmärkt - -subscribe.feed_url=Prenumerationens URL -subscribe.feed_name=Prenumerationens namn -subscribe.category=Kategori - -import.google_reader_prefix=Låt mig importera dina prenumerationer från ditt -import.google_reader_suffix=-konto. -import.google_download=Alternativt, ladda upp din subscriptions.xml-fil. -import.google_download_link=Ladda ned den här. -import.xml_file=OPML-fil - -new_category.name=Namn -new_category.parent=Överordnad - -toolbar.unread=Oläst -toolbar.all=Alla -toolbar.previous_entry=Föregående post -toolbar.next_entry=Nästa post -toolbar.refresh=Uppdatera -toolbar.refresh_all=Tvinga uppdatering av alla prenumerationer -toolbar.sort_by_asc_desc=Sortera efter datum stigande/fallande -toolbar.titles_only=Endast titlar -toolbar.expanded_view=Expanderad vy -toolbar.mark_all_as_read=Markera alla som lästa -toolbar.mark_all_older_12_hours=Poster äldre än 12 timmar -toolbar.mark_all_older_day=Poster äldre än en dag -toolbar.mark_all_older_week=Poster äldre än en vecka -toolbar.mark_all_older_two_weeks=Poster äldre än två veckor -toolbar.settings=Inställningar -toolbar.profile=Profil -toolbar.admin=Administratör -toolbar.about=Om -toolbar.logout=Logga ut -toolbar.donate=Donera - -view.entry_source=från -view.entry_author=av -view.error_while_loading_feed=Fel under laddning av denna prenumeration -view.keep_unread=Håll oläst -view.no_unread_items=har inga olästa poster. -view.mark_up_to_here=Markera som läst upp till denna post -view.search_for=söker efter: -view.no_search_results=Inga resultat för valda nyckelord - -feedsearch.hint=Skriv in en prenumeration... -feedsearch.help=Använd retur-tangenten för att välja och piltangenterna för att navigera. -feedsearch.result_prefix=Dina prenumerationer: - -settings.general=Allmänt -settings.general.language=Språk -settings.general.language.contribute=Bidra med översättningar -settings.general.show_unread=Visa prenumerationer och kategorier utan olästa poster -settings.general.social_buttons=Visa delningsknappar -settings.general.scroll_marks=I expanderad vy, markera poster som lästa genom att scrolla förbi dem -settings.appearance=Utseende -settings.theme=Tema -settings.submit_your_theme=Skicka in ditt tema -settings.custom_css=Anpassad CSS - -details.feed_details=Prenumerationsdetaljer -details.url=URL -details.website=Webbsida -details.name=Namn -details.category=Kategori -details.position=Position -details.last_refresh=Senaste uppdatering -details.message=Senaste uppdateringsmeddelande -details.next_refresh=Nästa uppdatering -details.queued_for_refresh=I kö för uppdatering -details.feed_url=Prenumerationens URL -details.generate_api_key_first=Skapa en API-nyckel på din profil först. -details.unsubscribe=Avprenumerera -details.category_details=Kategoridetaljer -details.parent_category=Överordnad kategori - -profile.user_name=Användarnamn -profile.email=E-mail -profile.change_password=Ändra lösenord -profile.confirm_password=Bekräfta lösenord -profile.minimum_6_chars=Minst 6 bokstäver -profile.passwords_do_not_match=Lösenorden matchar inte -profile.api_key=API-nyckel -profile.api_key_not_generated=Inte skapad än -profile.generate_new_api_key=Skapa ny API-nyckel -profile.generate_new_api_key_info=Lösenordsbyte skapar ny API-nyckel -profile.opml_export=OPML-export -profile.delete_account=Radera konto - -about.rest_api=REST-API -about.keyboard_shortcuts=Tangentbordsgenvägar -about.version=CommaFeed-version -about.line1_prefix=CommaFeed är ett open-source-projekt. Källan är tillgänglig på -about.line1_suffix=. -about.line2_prefix=Om du träffar på ett problem, meddela det på "Issues"-sidan för -about.line2_suffix=-projektet. -about.line3=Om du gillar detta projekt, avväg gärna en donation för att stötta utvecklaren och bidra till kostnaderna för att hålla denna site online. -about.line4=För er som föredrar Bitcoin, här är adressen -about.goodies=Godsaker -about.goodies.android_app=Android-app -about.goodies.subscribe_url=Prenumerations-URL -about.goodies.chrome_extension=Chrome-tillägg -about.goodies.firefox_extension=Firefox-tillägg -about.goodies.opera_extension=Opera-tillägg -about.goodies.subscribe_bookmarklet=Bokmärke för tillägg av prenumeration (klicka) -about.goodies.subscribe_bookmarklet_asc=äldst först -about.goodies.subscribe_bookmarklet_desc=nyast först -about.goodies.next_unread_bookmarklet=Bokmärke för nästa olästa post (dra till bokmärkesfält) -about.translation=Översättning -about.translation.message=Vi behöver din hjälp med att översätta CommaFeed. -about.translation.link=Se hur du kan bidra med översättningar. -about.announcements=Notiser -about.rest_api.line1=CommaFeed är byggt på JAX-RS och AngularJS. Tack vare detta är en REST-API tillgänglig. -about.rest_api.link_to_documentation=Länk till dokumentation. - -about.shortcuts.mouse_middleclick=mitten-musknapp -about.shortcuts.open_next_entry=öppna nästa post -about.shortcuts.open_previous_entry=öppna föregående post -about.shortcuts.spacebar=mellanslag/shift+mellanslag -about.shortcuts.move_page_down_up=flyttar sidan ned/upp -about.shortcuts.focus_next_entry=sätt fokus på nästa post utan att öppna -about.shortcuts.focus_previous_entry=sätt fokus på föregående post utan att öppna -about.shortcuts.open_next_feed=öppna nästa prenumeration eller kategori -about.shortcuts.open_previous_feed=öppna föregående prenumeration eller kategori -about.shortcuts.open_close_current_entry=öppna/stäng nuvarande post -about.shortcuts.open_current_entry_in_new_window=öppna nuvarande post i nytt fönster -about.shortcuts.open_current_entry_in_new_window_background=öppna nuvarande post i nytt bakgrundsfönster -about.shortcuts.star_unstar=stjärnmärk/ostjärnmärk nuvarande post -about.shortcuts.mark_current_entry=markera nuvarande post läst/oläst -about.shortcuts.mark_all_as_read=markera alla som lästa -about.shortcuts.open_in_new_tab_mark_as_read=öppna nuvarande post i ny flik och markera som läst -about.shortcuts.fullscreen=växla till/från fullskärmsläge -about.shortcuts.font_size=öka/minska teckenstorlek av nuvarande post -about.shortcuts.go_to_all=se alla poster -about.shortcuts.go_to_starred=se stjärnmärkta poster -about.shortcuts.feed_search=navigera till en prenumeration via prenumerationsnamn - +global.save=Spara +global.cancel=Avbryt +global.delete=Radera +global.required=Obligatorisk +global.download=Ladda ned +global.link=Länka +global.bookmark=Bokmärk +global.close=Stäng +global.tags=Tags ####### Needs translation + +tree.subscribe=Prenumerera +tree.import=Importera +tree.new_category=Ny kategori +tree.all=Alla +tree.starred=Stjärnmärkt + +subscribe.feed_url=Prenumerationens URL +subscribe.feed_name=Prenumerationens namn +subscribe.category=Kategori + +import.google_reader_prefix=Låt mig importera dina prenumerationer från ditt +import.google_reader_suffix=-konto. +import.google_download=Alternativt, ladda upp din subscriptions.xml-fil. +import.google_download_link=Ladda ned den här. +import.xml_file=OPML-fil + +new_category.name=Namn +new_category.parent=Överordnad + +toolbar.unread=Oläst +toolbar.all=Alla +toolbar.previous_entry=Föregående post +toolbar.next_entry=Nästa post +toolbar.refresh=Uppdatera +toolbar.refresh_all=Tvinga uppdatering av alla prenumerationer +toolbar.sort_by_asc_desc=Sortera efter datum stigande/fallande +toolbar.titles_only=Endast titlar +toolbar.expanded_view=Expanderad vy +toolbar.mark_all_as_read=Markera alla som lästa +toolbar.mark_all_older_12_hours=Poster äldre än 12 timmar +toolbar.mark_all_older_day=Poster äldre än en dag +toolbar.mark_all_older_week=Poster äldre än en vecka +toolbar.mark_all_older_two_weeks=Poster äldre än två veckor +toolbar.settings=Inställningar +toolbar.profile=Profil +toolbar.admin=Administratör +toolbar.about=Om +toolbar.logout=Logga ut +toolbar.donate=Donera + +view.entry_source=från +view.entry_author=av +view.error_while_loading_feed=Fel under laddning av denna prenumeration +view.keep_unread=Håll oläst +view.no_unread_items=har inga olästa poster. +view.mark_up_to_here=Markera som läst upp till denna post +view.search_for=söker efter: +view.no_search_results=Inga resultat för valda nyckelord + +feedsearch.hint=Skriv in en prenumeration... +feedsearch.help=Använd retur-tangenten för att välja och piltangenterna för att navigera. +feedsearch.result_prefix=Dina prenumerationer: + +settings.general=Allmänt +settings.general.language=Språk +settings.general.language.contribute=Bidra med översättningar +settings.general.show_unread=Visa prenumerationer och kategorier utan olästa poster +settings.general.social_buttons=Visa delningsknappar +settings.general.scroll_marks=I expanderad vy, markera poster som lästa genom att scrolla förbi dem +settings.appearance=Utseende +settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation +settings.scroll_speed.help=set to 0 to disable ####### Needs translation +settings.theme=Tema +settings.submit_your_theme=Skicka in ditt tema +settings.custom_css=Anpassad CSS + +details.feed_details=Prenumerationsdetaljer +details.url=URL +details.website=Webbsida +details.name=Namn +details.category=Kategori +details.position=Position +details.last_refresh=Senaste uppdatering +details.message=Senaste uppdateringsmeddelande +details.next_refresh=Nästa uppdatering +details.queued_for_refresh=I kö för uppdatering +details.feed_url=Prenumerationens URL +details.generate_api_key_first=Skapa en API-nyckel på din profil först. +details.unsubscribe=Avprenumerera +details.category_details=Kategoridetaljer +details.tag_details=Tag details ####### Needs translation +details.parent_category=Överordnad kategori + +profile.user_name=Användarnamn +profile.email=E-mail +profile.change_password=Ändra lösenord +profile.confirm_password=Bekräfta lösenord +profile.minimum_6_chars=Minst 6 bokstäver +profile.passwords_do_not_match=Lösenorden matchar inte +profile.api_key=API-nyckel +profile.api_key_not_generated=Inte skapad än +profile.generate_new_api_key=Skapa ny API-nyckel +profile.generate_new_api_key_info=Lösenordsbyte skapar ny API-nyckel +profile.opml_export=OPML-export +profile.delete_account=Radera konto + +about.rest_api=REST-API +about.keyboard_shortcuts=Tangentbordsgenvägar +about.version=CommaFeed-version +about.line1_prefix=CommaFeed är ett open-source-projekt. Källan är tillgänglig på +about.line1_suffix=. +about.line2_prefix=Om du träffar på ett problem, meddela det på "Issues"-sidan för +about.line2_suffix=-projektet. +about.line3=Om du gillar detta projekt, avväg gärna en donation för att stötta utvecklaren och bidra till kostnaderna för att hålla denna site online. +about.line4=För er som föredrar Bitcoin, här är adressen +about.goodies=Godsaker +about.goodies.android_app=Android-app +about.goodies.subscribe_url=Prenumerations-URL +about.goodies.chrome_extension=Chrome-tillägg +about.goodies.firefox_extension=Firefox-tillägg +about.goodies.opera_extension=Opera-tillägg +about.goodies.subscribe_bookmarklet=Bokmärke för tillägg av prenumeration (klicka) +about.goodies.subscribe_bookmarklet_asc=äldst först +about.goodies.subscribe_bookmarklet_desc=nyast först +about.goodies.next_unread_bookmarklet=Bokmärke för nästa olästa post (dra till bokmärkesfält) +about.translation=Översättning +about.translation.message=Vi behöver din hjälp med att översätta CommaFeed. +about.translation.link=Se hur du kan bidra med översättningar. +about.announcements=Notiser +about.rest_api.line1=CommaFeed är byggt på JAX-RS och AngularJS. Tack vare detta är en REST-API tillgänglig. +about.rest_api.link_to_documentation=Länk till dokumentation. + +about.shortcuts.mouse_middleclick=mitten-musknapp +about.shortcuts.open_next_entry=öppna nästa post +about.shortcuts.open_previous_entry=öppna föregående post +about.shortcuts.spacebar=mellanslag/shift+mellanslag +about.shortcuts.move_page_down_up=flyttar sidan ned/upp +about.shortcuts.focus_next_entry=sätt fokus på nästa post utan att öppna +about.shortcuts.focus_previous_entry=sätt fokus på föregående post utan att öppna +about.shortcuts.open_next_feed=öppna nästa prenumeration eller kategori +about.shortcuts.open_previous_feed=öppna föregående prenumeration eller kategori +about.shortcuts.open_close_current_entry=öppna/stäng nuvarande post +about.shortcuts.open_current_entry_in_new_window=öppna nuvarande post i nytt fönster +about.shortcuts.open_current_entry_in_new_window_background=öppna nuvarande post i nytt bakgrundsfönster +about.shortcuts.star_unstar=stjärnmärk/ostjärnmärk nuvarande post +about.shortcuts.mark_current_entry=markera nuvarande post läst/oläst +about.shortcuts.mark_all_as_read=markera alla som lästa +about.shortcuts.open_in_new_tab_mark_as_read=öppna nuvarande post i ny flik och markera som läst +about.shortcuts.fullscreen=växla till/från fullskärmsläge +about.shortcuts.font_size=öka/minska teckenstorlek av nuvarande post +about.shortcuts.go_to_all=se alla poster +about.shortcuts.go_to_starred=se stjärnmärkta poster +about.shortcuts.feed_search=navigera till en prenumeration via prenumerationsnamn + diff --git a/src/main/resources/i18n/tr.properties b/src/main/resources/i18n/tr.properties index c8446eef..06cfd950 100644 --- a/src/main/resources/i18n/tr.properties +++ b/src/main/resources/i18n/tr.properties @@ -6,6 +6,7 @@ global.download=İndir global.link=Bağlantı global.bookmark=Yer imi global.close=Kapat +global.tags=Tags ####### Needs translation tree.subscribe=Abone ol tree.import=İçe aktar @@ -67,6 +68,8 @@ settings.general.show_unread=Okunmamış öğesi bulunan yayın ve kategorileri settings.general.social_buttons=Sosyal paylaşım butonlarını göster settings.general.scroll_marks=Genişletilmiş görünümde götüntülenen iletileri okunmuş işaretle settings.appearance=Görünüm +settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation +settings.scroll_speed.help=set to 0 to disable ####### Needs translation settings.theme=Tema settings.submit_your_theme=Tema gönder settings.custom_css=Kişiselleştirilmiş CSS @@ -85,6 +88,7 @@ details.feed_url=Yayın URL'si details.generate_api_key_first=Öncelikle profilinizden bir API anahtarı oluşturun. details.unsubscribe=Aboneliği iptal et details.category_details=Kategori detayları +details.tag_details=Tag details ####### Needs translation details.parent_category=Üst kategori profile.user_name=Kullanıcı adı diff --git a/src/main/resources/i18n/zh.properties b/src/main/resources/i18n/zh.properties index 647c8cd2..e0a150e9 100644 --- a/src/main/resources/i18n/zh.properties +++ b/src/main/resources/i18n/zh.properties @@ -6,6 +6,7 @@ global.download=下载 global.link=链接 global.bookmark=书签 global.close=关闭 +global.tags=Tags ####### Needs translation tree.subscribe=订阅 tree.import=导入 @@ -67,6 +68,8 @@ settings.general.show_unread=显示未读的订阅和目录条目 settings.general.social_buttons=显示分享按钮 settings.general.scroll_marks=在扩展视图中,可滚动条目将其标记为已读 settings.appearance=外观 +settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation +settings.scroll_speed.help=set to 0 to disable ####### Needs translation settings.theme=主题 settings.submit_your_theme=提交你的主题 settings.custom_css=自定义 CSS 样式 @@ -85,6 +88,7 @@ details.feed_url=订阅地址 details.generate_api_key_first=在您的配置文件中首先生成一个 API 密钥。 details.unsubscribe=取消订阅 details.category_details=目录详情 +details.tag_details=Tag details ####### Needs translation details.parent_category=上一层目录 profile.user_name=用户名 diff --git a/src/main/webapp/js/controllers.js b/src/main/webapp/js/controllers.js index 401ac976..cc6a9397 100644 --- a/src/main/webapp/js/controllers.js +++ b/src/main/webapp/js/controllers.js @@ -125,17 +125,40 @@ module.controller('SubscribeCtrl', ['$scope', 'FeedService', 'CategoryService', }]); module.controller('CategoryTreeCtrl', ['$scope', '$timeout', '$stateParams', '$window', '$location', '$state', '$route', 'CategoryService', - 'AnalyticsService', - function($scope, $timeout, $stateParams, $window, $location, $state, $route, CategoryService, AnalyticsService) { + 'AnalyticsService', 'EntryService', + function($scope, $timeout, $stateParams, $window, $location, $state, $route, CategoryService, AnalyticsService, EntryService) { $scope.selectedType = $stateParams._type; $scope.selectedId = $stateParams._id; + $scope.EntryService = EntryService; + $scope.starred = { id : 'starred', name : 'Starred' }; + $scope.tags = []; + $scope.$watch('EntryService.tags', function(newValue, oldValue) { + if (newValue) { + $scope.tags = []; + _.each(newValue, function(e) { + $scope.tags.push({ + id : e, + name : e, + isTag : true + }); + }); + } + }, true); + + $scope.toTag = function(tag) { + $state.transitionTo('feeds.view', { + _type : 'tag', + _id : tag + }); + }; + $scope.$on('$stateChangeSuccess', function() { $scope.selectedType = $stateParams._type; $scope.selectedId = $stateParams._id; @@ -201,7 +224,7 @@ module.controller('CategoryTreeCtrl', ['$scope', '$timeout', '$stateParams', '$w var getCurrentIndex = function(id, type, flat) { var index = -1; - for ( var i = 0; i < flat.length; i++) { + for (var i = 0; i < flat.length; i++) { var node = flat[i]; if (node[0] == id && node[1] == type) { index = i; @@ -355,7 +378,7 @@ module.controller('CategoryDetailsCtrl', ['$scope', '$state', '$stateParams', 'F }; return; } - for ( var i = 0; i < CategoryService.flatCategories.length; i++) { + for (var i = 0; i < CategoryService.flatCategories.length; i++) { var cat = CategoryService.flatCategories[i]; if (cat.id == $stateParams._id) { $scope.category = { @@ -423,6 +446,65 @@ module.controller('CategoryDetailsCtrl', ['$scope', '$state', '$stateParams', 'F }; }]); +module.controller('TagDetailsCtrl', ['$scope', '$state', '$stateParams', 'FeedService', 'CategoryService', 'ProfileService', '$dialog', + function($scope, $state, $stateParams, FeedService, CategoryService, ProfileService, $dialog) { + $scope.CategoryService = CategoryService; + $scope.user = ProfileService.get(); + + $scope.tag = $stateParams._id; + + $scope.back = function() { + $state.transitionTo('feeds.view', { + _id : $scope.tag, + _type : 'tag' + }); + }; + + $scope.deleteTag = function() { + var category = $scope.category; + var title = 'Delete tag'; + var msg = 'Delete tag ' + tag + ' ?'; + var btns = [{ + result : 'cancel', + label : 'Cancel' + }, { + result : 'ok', + label : 'OK', + cssClass : 'btn-primary' + }]; + + $dialog.messageBox(title, msg, btns).open().then(function(result) { + if (result == 'ok') { + CategoryService.remove({ + id : category.id + }, function() { + CategoryService.init(); + }); + $state.transitionTo('feeds.view', { + _id : 'all', + _type : 'category' + }); + } + }); + }; + + $scope.save = function() { + var cat = $scope.category; + CategoryService.modify({ + id : cat.id, + name : cat.name, + position : cat.position, + parentId : cat.parentId + }, function() { + CategoryService.init(); + $state.transitionTo('feeds.view', { + _id : 'all', + _type : 'category' + }); + }); + }; + }]); + module.controller('ToolbarCtrl', [ '$scope', '$http', @@ -501,7 +583,7 @@ module.controller('ToolbarCtrl', [ $scope.markAllAsRead = function() { markAll(); }; - + $scope.markAll12Hours = function() { markAll(new Date().getTime() - 43200000); }; @@ -574,7 +656,7 @@ module.controller('FeedSearchCtrl', ['$scope', '$state', '$filter', '$timeout', } var filtered = $scope.filtered; - for ( var i = 0; i < filtered.length; i++) { + for (var i = 0; i < filtered.length; i++) { if ($scope.focus.id == filtered[i].id) { index = i; break; @@ -706,6 +788,12 @@ module.controller('FeedListCtrl', [ } }); + $scope.$watch('settingsService.settings.readingOrder', function(newValue, oldValue) { + if (newValue && oldValue && newValue != oldValue) { + $scope.$emit('emitReload'); + } + }); + $scope.limit = SettingsService.settings.viewMode == 'title' ? 10 : 5; $scope.busy = false; $scope.hasMore = true; @@ -733,7 +821,7 @@ module.controller('FeedListCtrl', [ } var callback = function(data) { - for ( var i = 0; i < data.entries.length; i++) { + for (var i = 0; i < data.entries.length; i++) { var entry = data.entries[i]; if (!_.some($scope.entries, { id : entry.id @@ -750,15 +838,23 @@ module.controller('FeedListCtrl', [ $scope.feedLink = data.feedLink; }; - var service = $scope.selectedType == 'feed' ? FeedService : CategoryService; - service.entries({ + var data = { id : $scope.selectedId, readType : $scope.keywords ? 'all' : $scope.settingsService.settings.readingMode, order : $scope.settingsService.settings.readingOrder, offset : offset, limit : limit, keywords : $scope.keywords - }, callback); + }; + if ($scope.selectedType == 'feed') { + FeedService.entries(data, callback); + } else if ($scope.selectedType == 'category') { + CategoryService.entries(data, callback); + } else if ($scope.selectedType == 'tag') { + data.tag = data.id; + data.id = 'all'; + CategoryService.entries(data, callback); + } }; var watch_scrolling = true; @@ -808,7 +904,7 @@ module.controller('FeedListCtrl', [ var docTop = w.scrollTop(); var current = null; - for ( var i = 0; i < $scope.entries.length; i++) { + for (var i = 0; i < $scope.entries.length; i++) { var entry = $scope.entries[i]; var e = $('#entry_' + entry.id); if (e.offset().top + e.height() > docTop + $('#toolbar').outerHeight()) { @@ -871,7 +967,7 @@ module.controller('FeedListCtrl', [ $scope.markUpTo = function(entry) { var entries = []; - for ( var i = 0; i < $scope.entries.length; i++) { + for (var i = 0; i < $scope.entries.length; i++) { var e = $scope.entries[i]; if (!e.read) { entries.push({ @@ -910,7 +1006,7 @@ module.controller('FeedListCtrl', [ var getCurrentIndex = function() { var index = -1; if ($scope.current) { - for ( var i = 0; i < $scope.entries.length; i++) { + for (var i = 0; i < $scope.entries.length; i++) { if ($scope.current == $scope.entries[i]) { index = i; break; @@ -1351,7 +1447,7 @@ module.controller('ManageDuplicateFeedsCtrl', ['$scope', 'AdminCleanupService', var callback = function() { alert('done!'); }; - for ( var i = 0; i < $scope.counts.length; i++) { + for (var i = 0; i < $scope.counts.length; i++) { var count = $scope.counts[i]; if (count.autoMerge) { AdminCleanupService.mergeFeeds({ diff --git a/src/main/webapp/js/directives.js b/src/main/webapp/js/directives.js index ab9e7ab7..79f80c9b 100644 --- a/src/main/webapp/js/directives.js +++ b/src/main/webapp/js/directives.js @@ -30,6 +30,38 @@ module.directive('popup', function() { }; }); +/** + * entry tag handling + */ +module.directive('tags', function() { + return { + restrict : 'E', + scope : { + entry : '=' + }, + replace : true, + templateUrl : 'templates/_tags.html', + controller : ['$scope', 'EntryService', function($scope, EntryService) { + $scope.select2Options = { + 'multiple' : true, + 'simple_tags' : true, + 'maximumInputLength' : 40, + tags : EntryService.tags + }; + + $scope.$watch('entry.tags', function(newValue, oldValue) { + if (newValue && oldValue && newValue != oldValue) { + var data = { + entryId : $scope.entry.id, + tags : newValue + }; + EntryService.tag(data); + } + }, true); + }] + }; +}); + /** * Reusable favicon component */ @@ -116,7 +148,8 @@ module.directive('category', [function() { selectedId : '=', showLabel : '=', showChildren : '=', - unreadCount : '&' + unreadCount : '&', + tag : '=' }, restrict : 'E', replace : true, @@ -174,13 +207,14 @@ module.directive('category', [function() { } }; - $scope.categoryClicked = function(id) { + $scope.categoryClicked = function(id, isTag) { MobileService.toggleLeftMenu(); - if ($scope.selectedType == 'category' && id == $scope.selectedId) { + var type = isTag ? 'tag' : 'category'; + if ($scope.selectedType == type && id == $scope.selectedId) { $scope.$emit('emitReload'); } else { $state.transitionTo('feeds.view', { - _type : 'category', + _type : type, _id : id }); } @@ -192,10 +226,16 @@ module.directive('category', [function() { }); }; - $scope.showCategoryDetails = function(category) { - $state.transitionTo('feeds.category_details', { - _id : category.id - }); + $scope.showCategoryDetails = function(id, isTag) { + if (isTag) { + $state.transitionTo('feeds.tag_details', { + _id : id + }); + } else { + $state.transitionTo('feeds.category_details', { + _id : id + }); + } }; $scope.toggleCategory = function(category, event) { diff --git a/src/main/webapp/js/main.js b/src/main/webapp/js/main.js index 1b20aba1..aa045831 100644 --- a/src/main/webapp/js/main.js +++ b/src/main/webapp/js/main.js @@ -54,6 +54,11 @@ app.config(['$routeProvider', '$stateProvider', '$urlRouterProvider', '$httpProv templateUrl : 'templates/feeds.category_details.html', controller : 'CategoryDetailsCtrl' }); + $stateProvider.state('feeds.tag_details', { + url : '/details/tag/:_id', + templateUrl : 'templates/feeds.tag_details.html', + controller : 'TagDetailsCtrl' + }); $stateProvider.state('feeds.help', { url : '/help', templateUrl : 'templates/feeds.help.html', @@ -105,7 +110,7 @@ app.config(['$routeProvider', '$stateProvider', '$urlRouterProvider', '$httpProv templateUrl : 'templates/admin.metrics.html', controller : 'MetricsCtrl' }); - + $urlRouterProvider.when('/', '/feeds/view/category/all'); $urlRouterProvider.when('/admin', '/admin/settings'); $urlRouterProvider.otherwise('/'); diff --git a/src/main/webapp/js/services.js b/src/main/webapp/js/services.js index d9e6fc84..b9062ccd 100644 --- a/src/main/webapp/js/services.js +++ b/src/main/webapp/js/services.js @@ -126,7 +126,7 @@ module.factory('CategoryService', ['$resource', '$http', function($resource, $ht callback(category, parentName); var children = category.children; if (children) { - for ( var c = 0; c < children.length; c++) { + for (var c = 0; c < children.length; c++) { traverse(callback, children[c], category.name); } } @@ -271,9 +271,29 @@ module.factory('EntryService', ['$resource', '$http', function($resource, $http) params : { _method : 'star' } + }, + tag : { + method : 'POST', + params : { + _method : 'tag' + } } }; var res = $resource('rest/entry/:_method', {}, actions); + res.tags = []; + var initTags = function() { + $http.get('rest/entry/tags').success(function(data) { + res.tags = []; + res.tags.push.apply(res.tags, data); + }); + }; + var oldTag = res.tag; + res.tag = function(data) { + oldTag(data, function() { + initTags(); + }); + }; + initTags(); return res; }]); @@ -296,7 +316,6 @@ module.factory('AdminMetricsService', ['$resource', function($resource) { return res; }]); - module.factory('AdminCleanupService', ['$resource', function($resource) { var actions = { findDuplicateFeeds : { diff --git a/src/main/webapp/sass/components/_entry-list.scss b/src/main/webapp/sass/components/_entry-list.scss index cc81f98f..5e63e04b 100644 --- a/src/main/webapp/sass/components/_entry-list.scss +++ b/src/main/webapp/sass/components/_entry-list.scss @@ -143,7 +143,7 @@ } .full-screen #feed-accordion .entry-body-content { - max-width: 100%; + max-width: 100%; } #feed-accordion .entry-enclosure { @@ -172,6 +172,24 @@ text-decoration: none; } +#feed-accordion .tags-panel { + margin-left: 30px; +} + +#feed-accordion .tags-panel .label{ + margin-left: 5px; +} + +.select2-container-multi .select2-choices .select2-search-field input { + padding: 2px +} + +#feed-accordion .tag-input { + margin: 0 0 0 5px; + padding: 0; + width: 200px; +} + #feed-accordion .entry-buttons label { margin-bottom: 0px; font-size: 12px; diff --git a/src/main/webapp/sass/components/_subscription-list.scss b/src/main/webapp/sass/components/_subscription-list.scss index c45661ff..b0f951b3 100644 --- a/src/main/webapp/sass/components/_subscription-list.scss +++ b/src/main/webapp/sass/components/_subscription-list.scss @@ -30,7 +30,7 @@ } .sidebar-nav-fixed:hover { - overflow-y: auto; + overflow-y: auto; } .full-screen .left-menu { @@ -39,6 +39,7 @@ .css-treeview { margin-top: 15px; + margin-bottom: 30px; font-family: inherit; font-size: 13px; white-space: nowrap; diff --git a/src/main/webapp/sass/generic/_misc.scss b/src/main/webapp/sass/generic/_misc.scss index a8026476..a376cdac 100644 --- a/src/main/webapp/sass/generic/_misc.scss +++ b/src/main/webapp/sass/generic/_misc.scss @@ -2,6 +2,15 @@ cursor: pointer; } +.nolink { + color: inherit; +} + +.nolink:hover { + text-decoration: none; + color: inherit; +} + .block { display: block; } diff --git a/src/main/webapp/sass/mobile/_mobile.scss b/src/main/webapp/sass/mobile/_mobile.scss index 8aa54ea0..3997e3ab 100644 --- a/src/main/webapp/sass/mobile/_mobile.scss +++ b/src/main/webapp/sass/mobile/_mobile.scss @@ -40,6 +40,9 @@ right: 35px; margin-top: 22px; } + #feed-accordion .tags-panel { + display: block; + } body.left-menu-active .left-menu { display: block !important; width: 100%; @@ -61,4 +64,4 @@ #uvTab { display: none; } -} +} \ No newline at end of file diff --git a/src/main/webapp/templates/_category.html b/src/main/webapp/templates/_category.html index 61a9a58d..7f6fa68c 100644 --- a/src/main/webapp/templates/_category.html +++ b/src/main/webapp/templates/_category.html @@ -1,14 +1,14 @@