tagging support (#96)

This commit is contained in:
Athou
2013-10-13 10:49:44 +02:00
parent 94f469a6b1
commit 431ab92a02
53 changed files with 840 additions and 234 deletions

View File

@@ -31,6 +31,8 @@ import com.commafeed.backend.model.FeedEntry;
import com.commafeed.backend.model.FeedEntryContent_; import com.commafeed.backend.model.FeedEntryContent_;
import com.commafeed.backend.model.FeedEntryStatus; import com.commafeed.backend.model.FeedEntryStatus;
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.FeedEntry_;
import com.commafeed.backend.model.FeedSubscription; import com.commafeed.backend.model.FeedSubscription;
import com.commafeed.backend.model.Models; import com.commafeed.backend.model.Models;
@@ -47,6 +49,10 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
private static final String ALIAS_STATUS = "status"; private static final String ALIAS_STATUS = "status";
private static final String ALIAS_ENTRY = "entry"; private static final String ALIAS_ENTRY = "entry";
private static final String ALIAS_TAG = "tag";
@Inject
FeedEntryTagDAO feedEntryTagDAO;
private static final Comparator<FeedEntryStatus> STATUS_COMPARATOR_DESC = new Comparator<FeedEntryStatus>() { private static final Comparator<FeedEntryStatus> STATUS_COMPARATOR_DESC = new Comparator<FeedEntryStatus>() {
@Override @Override
@@ -63,7 +69,7 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
@Inject @Inject
ApplicationSettingsService applicationSettingsService; ApplicationSettingsService applicationSettingsService;
public FeedEntryStatus getStatus(FeedSubscription sub, FeedEntry entry) { public FeedEntryStatus getStatus(User user, FeedSubscription sub, FeedEntry entry) {
CriteriaQuery<FeedEntryStatus> query = builder.createQuery(getType()); CriteriaQuery<FeedEntryStatus> query = builder.createQuery(getType());
Root<FeedEntryStatus> root = query.from(getType()); Root<FeedEntryStatus> root = query.from(getType());
@@ -76,14 +82,14 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
List<FeedEntryStatus> statuses = em.createQuery(query).getResultList(); List<FeedEntryStatus> statuses = em.createQuery(query).getResultList();
FeedEntryStatus status = Iterables.getFirst(statuses, null); 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) { if (status == null) {
Date unreadThreshold = applicationSettingsService.getUnreadThreshold(); Date unreadThreshold = applicationSettingsService.getUnreadThreshold();
boolean read = unreadThreshold == null ? false : entry.getUpdated().before(unreadThreshold); 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.setRead(read);
status.setMarkable(!read); status.setMarkable(!read);
} else { } else {
@@ -92,6 +98,12 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
return status; return status;
} }
private FeedEntryStatus fetchTags(User user, FeedEntryStatus status) {
List<FeedEntryTag> tags = feedEntryTagDAO.findByEntry(user, status.getEntry());
status.setTags(tags);
return status;
}
public List<FeedEntryStatus> findStarred(User user, Date newerThan, int offset, int limit, ReadingOrder order, boolean includeContent) { public List<FeedEntryStatus> findStarred(User user, Date newerThan, int offset, int limit, ReadingOrder order, boolean includeContent) {
CriteriaQuery<FeedEntryStatus> query = builder.createQuery(getType()); CriteriaQuery<FeedEntryStatus> query = builder.createQuery(getType());
@@ -114,13 +126,14 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
setTimeout(q); setTimeout(q);
List<FeedEntryStatus> statuses = q.getResultList(); List<FeedEntryStatus> statuses = q.getResultList();
for (FeedEntryStatus status : statuses) { 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); return lazyLoadContent(includeContent, statuses);
} }
private Criteria buildSearchCriteria(FeedSubscription sub, boolean unreadOnly, String keywords, Date newerThan, int offset, int limit, 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 criteria = getSession().createCriteria(FeedEntry.class, ALIAS_ENTRY);
criteria.add(Restrictions.eq(FeedEntry_.feed.getName(), sub.getFeed())); criteria.add(Restrictions.eq(FeedEntry_.feed.getName(), sub.getFeed()));
@@ -138,7 +151,7 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
Criteria statusJoin = criteria.createCriteria(FeedEntry_.statuses.getName(), ALIAS_STATUS, JoinType.LEFT_OUTER_JOIN, Criteria statusJoin = criteria.createCriteria(FeedEntry_.statuses.getName(), ALIAS_STATUS, JoinType.LEFT_OUTER_JOIN,
Restrictions.eq(FeedEntryStatus_.subscription.getName(), sub)); Restrictions.eq(FeedEntryStatus_.subscription.getName(), sub));
if (unreadOnly) { if (unreadOnly && tag == null) {
Disjunction or = Restrictions.disjunction(); Disjunction or = Restrictions.disjunction();
or.add(Restrictions.isNull(FeedEntryStatus_.read.getName())); or.add(Restrictions.isNull(FeedEntryStatus_.read.getName()));
@@ -151,6 +164,11 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
} }
} }
if (tag != null) {
criteria.createCriteria(FeedEntry_.tags.getName(), ALIAS_TAG, JoinType.INNER_JOIN,
Restrictions.eq(FeedEntryTag_.name.getName(), tag));
}
if (newerThan != null) { if (newerThan != null) {
criteria.add(Restrictions.ge(FeedEntry_.inserted.getName(), newerThan)); criteria.add(Restrictions.ge(FeedEntry_.inserted.getName(), newerThan));
} }
@@ -185,14 +203,14 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public List<FeedEntryStatus> findBySubscriptions(List<FeedSubscription> subs, boolean unreadOnly, String keywords, Date newerThan, public List<FeedEntryStatus> findBySubscriptions(User user, List<FeedSubscription> subs, boolean unreadOnly, String keywords,
int offset, int limit, ReadingOrder order, boolean includeContent, boolean onlyIds) { Date newerThan, int offset, int limit, ReadingOrder order, boolean includeContent, boolean onlyIds, String tag) {
int capacity = offset + limit; int capacity = offset + limit;
Comparator<FeedEntryStatus> comparator = order == ReadingOrder.desc ? STATUS_COMPARATOR_DESC : STATUS_COMPARATOR_ASC; Comparator<FeedEntryStatus> comparator = order == ReadingOrder.desc ? STATUS_COMPARATOR_DESC : STATUS_COMPARATOR_ASC;
FixedSizeSortedSet<FeedEntryStatus> set = new FixedSizeSortedSet<FeedEntryStatus>(capacity, comparator); FixedSizeSortedSet<FeedEntryStatus> set = new FixedSizeSortedSet<FeedEntryStatus>(capacity, comparator);
for (FeedSubscription sub : subs) { for (FeedSubscription sub : subs) {
Date last = (order != null && set.isFull()) ? set.last().getEntryUpdated() : null; 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(); ProjectionList projection = Projections.projectionList();
projection.add(Projections.property("id"), "id"); projection.add(Projections.property("id"), "id");
projection.add(Projections.property("updated"), "updated"); projection.add(Projections.property("updated"), "updated");
@@ -234,7 +252,10 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
for (FeedEntryStatus placeholder : placeholders) { for (FeedEntryStatus placeholder : placeholders) {
Long statusId = placeholder.getId(); Long statusId = placeholder.getId();
FeedEntry entry = em.find(FeedEntry.class, placeholder.getEntry().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); statuses = lazyLoadContent(includeContent, statuses);
} }
@@ -244,7 +265,7 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public UnreadCount getUnreadCount(FeedSubscription subscription) { public UnreadCount getUnreadCount(FeedSubscription subscription) {
UnreadCount uc = null; 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(); ProjectionList projection = Projections.projectionList();
projection.add(Projections.rowCount(), "count"); projection.add(Projections.rowCount(), "count");
projection.add(Projections.max(FeedEntry_.updated.getName()), "updated"); projection.add(Projections.max(FeedEntry_.updated.getName()), "updated");

View File

@@ -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<FeedEntryTag> {
public List<String> findByUser(User user) {
CriteriaQuery<String> query = builder.createQuery(String.class);
Root<FeedEntryTag> 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<FeedEntryTag> findByEntry(User user, FeedEntry entry) {
CriteriaQuery<FeedEntryTag> query = builder.createQuery(getType());
Root<FeedEntryTag> 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();
}
}

View File

@@ -55,5 +55,8 @@ public class FeedEntry extends AbstractModel {
@OneToMany(mappedBy = "entry", cascade = CascadeType.REMOVE) @OneToMany(mappedBy = "entry", cascade = CascadeType.REMOVE)
private Set<FeedEntryStatus> statuses; private Set<FeedEntryStatus> statuses;
@OneToMany(mappedBy = "entry", cascade = CascadeType.REMOVE)
private Set<FeedEntryTag> tags;
} }

View File

@@ -1,6 +1,7 @@
package com.commafeed.backend.model; package com.commafeed.backend.model;
import java.util.Date; import java.util.Date;
import java.util.List;
import javax.persistence.Cacheable; import javax.persistence.Cacheable;
import javax.persistence.Column; import javax.persistence.Column;
@@ -42,6 +43,9 @@ public class FeedEntryStatus extends AbstractModel {
@Transient @Transient
private boolean markable; private boolean markable;
@Transient
private List<FeedEntryTag> tags;
/** /**
* Denormalization starts here * Denormalization starts here

View File

@@ -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;
}
}

View File

@@ -43,7 +43,7 @@ public class FeedEntryService {
return; return;
} }
FeedEntryStatus status = feedEntryStatusDAO.getStatus(sub, entry); FeedEntryStatus status = feedEntryStatusDAO.getStatus(user, sub, entry);
if (status.isMarkable()) { if (status.isMarkable()) {
status.setRead(read); status.setRead(read);
feedEntryStatusDAO.saveOrUpdate(status); feedEntryStatusDAO.saveOrUpdate(status);
@@ -64,14 +64,14 @@ public class FeedEntryService {
return; return;
} }
FeedEntryStatus status = feedEntryStatusDAO.getStatus(sub, entry); FeedEntryStatus status = feedEntryStatusDAO.getStatus(user, sub, entry);
status.setStarred(starred); status.setStarred(starred);
feedEntryStatusDAO.saveOrUpdate(status); feedEntryStatusDAO.saveOrUpdate(status);
} }
public void markSubscriptionEntries(User user, List<FeedSubscription> subscriptions, Date olderThan) { public void markSubscriptionEntries(User user, List<FeedSubscription> subscriptions, Date olderThan) {
List<FeedEntryStatus> statuses = feedEntryStatusDAO List<FeedEntryStatus> statuses = feedEntryStatusDAO.findBySubscriptions(user, subscriptions, true, null, null, -1, -1, null, false,
.findBySubscriptions(subscriptions, true, null, null, -1, -1, null, false, false); false, null);
markList(statuses, olderThan); markList(statuses, olderThan);
cache.invalidateUnreadCount(subscriptions.toArray(new FeedSubscription[0])); cache.invalidateUnreadCount(subscriptions.toArray(new FeedSubscription[0]));
cache.invalidateUserRootCategory(user); cache.invalidateUserRootCategory(user);

View File

@@ -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<String> tagNames) {
FeedEntry entry = feedEntryDAO.findById(entryId);
if (entry == null) {
return;
}
List<FeedEntryTag> tags = feedEntryTagDAO.findByEntry(user, entry);
Map<String, FeedEntryTag> tagMap = Maps.uniqueIndex(tags, new Function<FeedEntryTag, String>() {
@Override
public String apply(FeedEntryTag input) {
return input.getName();
}
});
List<FeedEntryTag> addList = Lists.newArrayList();
List<FeedEntryTag> 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);
}
}

View File

@@ -3,6 +3,7 @@ package com.commafeed.frontend.model;
import java.io.Serializable; import java.io.Serializable;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.List;
import lombok.Data; import lombok.Data;
@@ -10,7 +11,9 @@ import com.commafeed.backend.feeds.FeedUtils;
import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedEntry;
import com.commafeed.backend.model.FeedEntryContent; 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.FeedSubscription; 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.SyndContentImpl;
import com.sun.syndication.feed.synd.SyndEntry; import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndEntryImpl; import com.sun.syndication.feed.synd.SyndEntryImpl;
@@ -43,6 +46,12 @@ public class Entry implements Serializable {
entry.setFeedLink(sub.getFeed().getLink()); entry.setFeedLink(sub.getFeed().getLink());
entry.setIconUrl(FeedUtils.getFaviconUrl(sub, publicUrl)); entry.setIconUrl(FeedUtils.getFaviconUrl(sub, publicUrl));
List<String> tags = Lists.newArrayList();
for (FeedEntryTag tag : status.getTags()) {
tags.add(tag.getName());
}
entry.setTags(tags);
if (content != null) { if (content != null) {
entry.setRtl(FeedUtils.isRTL(feedEntry)); entry.setRtl(FeedUtils.isRTL(feedEntry));
entry.setTitle(content.getTitle()); 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)") @ApiProperty("wether the entry is still markable (old entry statuses are discarded)")
private boolean markable; private boolean markable;
@ApiProperty("tags")
private List<String> tags;
} }

View File

@@ -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<String> tags;
}

View File

@@ -54,13 +54,13 @@ public class NextUnreadRedirectPage extends WebPage {
List<FeedEntryStatus> statuses = null; List<FeedEntryStatus> statuses = null;
if (StringUtils.isBlank(categoryId) || CategoryREST.ALL.equals(categoryId)) { if (StringUtils.isBlank(categoryId) || CategoryREST.ALL.equals(categoryId)) {
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user); List<FeedSubscription> 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 { } else {
FeedCategory category = feedCategoryDAO.findById(user, Long.valueOf(categoryId)); FeedCategory category = feedCategoryDAO.findById(user, Long.valueOf(categoryId));
if (category != null) { if (category != null) {
List<FeedCategory> children = feedCategoryDAO.findAllChildrenCategories(user, category); List<FeedCategory> children = feedCategoryDAO.findAllChildrenCategories(user, category);
List<FeedSubscription> subscriptions = feedSubscriptionDAO.findByCategories(user, children); List<FeedSubscription> 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);
} }
} }

View File

@@ -106,7 +106,8 @@ public class CategoryREST extends AbstractREST {
@ApiParam( @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, 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 = "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); Preconditions.checkNotNull(readType);
@@ -138,8 +139,8 @@ public class CategoryREST extends AbstractREST {
entries.setName("All"); entries.setName("All");
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(getUser()); List<FeedSubscription> subs = feedSubscriptionDAO.findAll(getUser());
removeExcludedSubscriptions(subs, excludedIds); removeExcludedSubscriptions(subs, excludedIds);
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(subs, unreadOnly, keywords, newerThanDate, offset, List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(getUser(), subs, unreadOnly, keywords, newerThanDate,
limit + 1, order, true, onlyIds); offset, limit + 1, order, true, onlyIds, tag);
for (FeedEntryStatus status : list) { for (FeedEntryStatus status : list) {
entries.getEntries().add( entries.getEntries().add(
@@ -161,8 +162,8 @@ public class CategoryREST extends AbstractREST {
List<FeedCategory> categories = feedCategoryDAO.findAllChildrenCategories(getUser(), parent); List<FeedCategory> categories = feedCategoryDAO.findAllChildrenCategories(getUser(), parent);
List<FeedSubscription> subs = feedSubscriptionDAO.findByCategories(getUser(), categories); List<FeedSubscription> subs = feedSubscriptionDAO.findByCategories(getUser(), categories);
removeExcludedSubscriptions(subs, excludedIds); removeExcludedSubscriptions(subs, excludedIds);
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(subs, unreadOnly, keywords, newerThanDate, offset, List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(getUser(), subs, unreadOnly, keywords, newerThanDate,
limit + 1, order, true, onlyIds); offset, limit + 1, order, true, onlyIds, tag);
for (FeedEntryStatus status : list) { for (FeedEntryStatus status : list) {
entries.getEntries().add( entries.getEntries().add(
@@ -192,7 +193,9 @@ public class CategoryREST extends AbstractREST {
@Produces(MediaType.APPLICATION_XML) @Produces(MediaType.APPLICATION_XML)
@SecurityCheck(value = Role.USER, apiKeyAllowed = true) @SecurityCheck(value = Role.USER, apiKeyAllowed = true)
public Response getCategoryEntriesAsFeed( 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); Preconditions.checkNotNull(id);
@@ -201,7 +204,7 @@ public class CategoryREST extends AbstractREST {
int offset = 0; int offset = 0;
int limit = 20; 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()) { if (response.getStatus() != Status.OK.getStatusCode()) {
return response; return response;
} }

View File

@@ -1,17 +1,23 @@
package com.commafeed.frontend.rest.resources; package com.commafeed.frontend.rest.resources;
import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.POST; import javax.ws.rs.POST;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import com.commafeed.backend.dao.FeedEntryStatusDAO; import com.commafeed.backend.dao.FeedEntryStatusDAO;
import com.commafeed.backend.dao.FeedEntryTagDAO;
import com.commafeed.backend.dao.FeedSubscriptionDAO; import com.commafeed.backend.dao.FeedSubscriptionDAO;
import com.commafeed.backend.services.ApplicationSettingsService; import com.commafeed.backend.services.ApplicationSettingsService;
import com.commafeed.backend.services.FeedEntryService; 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.MarkRequest;
import com.commafeed.frontend.model.request.MultipleMarkRequest; import com.commafeed.frontend.model.request.MultipleMarkRequest;
import com.commafeed.frontend.model.request.StarRequest; import com.commafeed.frontend.model.request.StarRequest;
import com.commafeed.frontend.model.request.TagRequest;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.wordnik.swagger.annotations.Api; import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiOperation; import com.wordnik.swagger.annotations.ApiOperation;
@@ -30,6 +36,12 @@ public class EntryREST extends AbstractREST {
@Inject @Inject
FeedSubscriptionDAO feedSubscriptionDAO; FeedSubscriptionDAO feedSubscriptionDAO;
@Inject
FeedEntryTagDAO feedEntryTagDAO;
@Inject
FeedEntryTagService feedEntryTagService;
@Inject @Inject
ApplicationSettingsService applicationSettingsService; ApplicationSettingsService applicationSettingsService;
@@ -71,4 +83,24 @@ public class EntryREST extends AbstractREST {
return Response.ok().build(); 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<String> 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();
}
} }

View File

@@ -167,8 +167,8 @@ public class FeedREST extends AbstractREST {
entries.setErrorCount(subscription.getFeed().getErrorCount()); entries.setErrorCount(subscription.getFeed().getErrorCount());
entries.setFeedLink(subscription.getFeed().getLink()); entries.setFeedLink(subscription.getFeed().getLink());
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(Arrays.asList(subscription), unreadOnly, keywords, List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(getUser(), Arrays.asList(subscription), unreadOnly,
newerThanDate, offset, limit + 1, order, true, onlyIds); keywords, newerThanDate, offset, limit + 1, order, true, onlyIds, null);
for (FeedEntryStatus status : list) { for (FeedEntryStatus status : list) {
entries.getEntries().add( entries.getEntries().add(

View File

@@ -9,8 +9,39 @@
</changeSet> </changeSet>
<changeSet author="athou" id="set-default-scroll-speed"> <changeSet author="athou" id="set-default-scroll-speed">
<update tableName="USERSETTINGS"> <update tableName="USERSETTINGS">
<column name="scroll_speed" valueNumeric="400" /> <column name="scroll_speed" valueNumeric="400" />
</update> </update>
</changeSet> </changeSet>
<changeSet author="athou" id="create-tags-table">
<createTable tableName="FEEDENTRYTAGS">
<column name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true" />
</column>
<column name="entry_id" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="user_id" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="name" type="VARCHAR(40)">
<constraints nullable="false" />
</column>
</createTable>
<addForeignKeyConstraint constraintName="fk_entry_id" baseTableName="FEEDENTRYTAGS" baseColumnNames="entry_id"
referencedTableName="FEEDENTRIES" referencedColumnNames="id" />
<addForeignKeyConstraint constraintName="fk_user_id" baseTableName="FEEDENTRYTAGS" baseColumnNames="user_id"
referencedTableName="USERS" referencedColumnNames="id" />
<createIndex tableName="FEEDENTRYTAGS" indexName="user_entry_index">
<column name="user_id" />
<column name="entry_id" />
</createIndex>
<createIndex tableName="FEEDENTRYTAGS" indexName="user_name_index">
<column name="user_id" />
<column name="name" />
</createIndex>
</changeSet>
</databaseChangeLog> </databaseChangeLog>

View File

@@ -6,6 +6,7 @@ global.download=تحميل
global.link=رابط global.link=رابط
global.bookmark=مرجعية global.bookmark=مرجعية
global.close=أغلق global.close=أغلق
global.tags=Tags ####### Needs translation
tree.subscribe=اشترك tree.subscribe=اشترك
tree.import=استورد 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.social_buttons=Show social sharing buttons
settings.general.scroll_marks=In expanded view, scrolling through entries mark them as read settings.general.scroll_marks=In expanded view, scrolling through entries mark them as read
settings.appearance=Appearance 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.theme=Theme
settings.submit_your_theme=Submit your theme settings.submit_your_theme=Submit your theme
settings.custom_css=Custom CSS 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.generate_api_key_first=Generate an API key in your profile first.
details.unsubscribe=Unsubscribe details.unsubscribe=Unsubscribe
details.category_details=Category details details.category_details=Category details
details.tag_details=Tag details ####### Needs translation
details.parent_category=Parent category details.parent_category=Parent category
profile.user_name=User name profile.user_name=User name

View File

@@ -6,6 +6,7 @@ global.download = Stáhnout
global.link = Odkaz global.link = Odkaz
global.bookmark = Záložky global.bookmark = Záložky
global.close = Zavřít global.close = Zavřít
global.tags=Tags ####### Needs translation
tree.subscribe = Nový odběr tree.subscribe = Nový odběr
tree.import = Importovat 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.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.general.scroll_marks = Skrolování v rozšířeném náhledu označí položky jako přečtené
settings.appearance = Vzhled 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.theme = Motiv
settings.submit_your_theme = Nahrát vlastní motiv settings.submit_your_theme = Nahrát vlastní motiv
settings.custom_css = Vlastní motiv (CSS) 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.generate_api_key_first = Vygenerujte si API klíč na stránce vašeho profilu.
details.unsubscribe = Odhlásit odběr. details.unsubscribe = Odhlásit odběr.
details.category_details = Detail kategorie details.category_details = Detail kategorie
details.tag_details=Tag details ####### Needs translation
details.parent_category = Hlavní kategorie details.parent_category = Hlavní kategorie
profile.user_name = Uživatelské jméno profile.user_name = Uživatelské jméno

View File

@@ -6,6 +6,7 @@ global.download=Lawrlwytho
global.link=Dolen global.link=Dolen
global.bookmark=Nod tudalen global.bookmark=Nod tudalen
global.close=Cau global.close=Cau
global.tags=Tags ####### Needs translation
tree.subscribe=Tanysgrifio tree.subscribe=Tanysgrifio
tree.import=Mewnforio 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.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.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.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.theme=Thema
settings.submit_your_theme=Cyflwyna dy thema settings.submit_your_theme=Cyflwyna dy thema
settings.custom_css=CSS wedi'i addasu 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.generate_api_key_first=Rhaid creu allwedd API yn dy broffil yn gyntaf.
details.unsubscribe=Dad-danysgrifio details.unsubscribe=Dad-danysgrifio
details.category_details=Manylion categori details.category_details=Manylion categori
details.tag_details=Tag details ####### Needs translation
details.parent_category=Categori rhiant details.parent_category=Categori rhiant
profile.user_name=Enw defnyddiwr profile.user_name=Enw defnyddiwr

View File

@@ -6,6 +6,7 @@ global.download=Hent
global.link=Link global.link=Link
global.bookmark=Bogmærke global.bookmark=Bogmærke
global.close=Luk global.close=Luk
global.tags=Tags ####### Needs translation
tree.subscribe=Abonner tree.subscribe=Abonner
tree.import=Importer 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.social_buttons=Vis delingsknapper
settings.general.scroll_marks=I udvidet visning, marker artikler som læste når der rulles forbi dem settings.general.scroll_marks=I udvidet visning, marker artikler som læste når der rulles forbi dem
settings.appearance=Udseende 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.theme=Tema
settings.submit_your_theme=Indsend dit tema settings.submit_your_theme=Indsend dit tema
settings.custom_css=Brugerdefineret CSS 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.generate_api_key_first=Generer en API nøgle i din profil først.
details.unsubscribe=Afmeld abonnement details.unsubscribe=Afmeld abonnement
details.category_details=Kategori detaljer details.category_details=Kategori detaljer
details.tag_details=Tag details ####### Needs translation
details.parent_category=Overordnet kategori details.parent_category=Overordnet kategori
profile.user_name=Brugernavn profile.user_name=Brugernavn

View File

@@ -6,6 +6,7 @@ global.download=Herunterladen
global.link=Link global.link=Link
global.bookmark=Lesezeichen global.bookmark=Lesezeichen
global.close=Schließen global.close=Schließen
global.tags=Tags ####### Needs translation
tree.subscribe=Abonnieren tree.subscribe=Abonnieren
tree.import=Importieren 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.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.general.scroll_marks=In der ausgedehnten Ansicht werden Artikel beim Scrollen als gelesen markiert
settings.appearance=Aussehen 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.theme=Theme
settings.submit_your_theme=Füg dein Theme hinzu settings.submit_your_theme=Füg dein Theme hinzu
settings.custom_css=Eigenes CSS 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.generate_api_key_first=Generiere zuerst einen API Schlüssel in deinem Profil.
details.unsubscribe=Kündigen details.unsubscribe=Kündigen
details.category_details=Kategoriedetails details.category_details=Kategoriedetails
details.tag_details=Tag details ####### Needs translation
details.parent_category=Übergeordnete Kategorie details.parent_category=Übergeordnete Kategorie
profile.user_name=Benutzername profile.user_name=Benutzername

View File

@@ -6,6 +6,7 @@ global.download=Download
global.link=Link global.link=Link
global.bookmark=Bookmark global.bookmark=Bookmark
global.close=Close global.close=Close
global.tags=Tags
tree.subscribe=Subscribe tree.subscribe=Subscribe
tree.import=Import 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.generate_api_key_first=Generate an API key in your profile first.
details.unsubscribe=Unsubscribe details.unsubscribe=Unsubscribe
details.category_details=Category details details.category_details=Category details
details.tag_details=Tag details
details.parent_category=Parent category details.parent_category=Parent category
profile.user_name=User name profile.user_name=User name

View File

@@ -6,6 +6,7 @@ global.download=Descargar
global.link=Enlace global.link=Enlace
global.bookmark=Marcador global.bookmark=Marcador
global.close=Close ####### Needs translation global.close=Close ####### Needs translation
global.tags=Tags ####### Needs translation
tree.subscribe=Subscribir tree.subscribe=Subscribir
tree.import=Importar 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.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.general.scroll_marks=En vista expandida, el desplazamiento por las entradas las marca como leídas
settings.appearance=Appearance ####### Needs translation 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.theme=Theme ####### Needs translation
settings.submit_your_theme=Submit your theme ####### Needs translation settings.submit_your_theme=Submit your theme ####### Needs translation
settings.custom_css=CSS Personalizado 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.generate_api_key_first=Genera una llave API en tu perfil primero.
details.unsubscribe=Terminar subscripción details.unsubscribe=Terminar subscripción
details.category_details=Detalles de la categoría details.category_details=Detalles de la categoría
details.tag_details=Tag details ####### Needs translation
details.parent_category=Categoría principal details.parent_category=Categoría principal
profile.user_name=Nombre de usuario profile.user_name=Nombre de usuario

View File

@@ -6,6 +6,7 @@ global.download=بارگیری
global.link=پیوند global.link=پیوند
global.bookmark=بوکمارک global.bookmark=بوکمارک
global.close=بستن global.close=بستن
global.tags=Tags ####### Needs translation
tree.subscribe=مشترک شوید tree.subscribe=مشترک شوید
tree.import=درون‌ریزی tree.import=درون‌ریزی
@@ -67,6 +68,8 @@ settings.general.show_unread=تنها خوراک‌ها و دسته‌های ر
settings.general.social_buttons=نشان‌دادن دکمه‌های اشتراک‌گذاری در شبکه‌های اجتماعی settings.general.social_buttons=نشان‌دادن دکمه‌های اشتراک‌گذاری در شبکه‌های اجتماعی
settings.general.scroll_marks=در نمای گسترش‌یافته، لغزیدن بر روی مطالب به‌عنوان نشانه‌گذاری به‌عنوان خوانده‌شده در نظر گرفته‌شوند. settings.general.scroll_marks=در نمای گسترش‌یافته، لغزیدن بر روی مطالب به‌عنوان نشانه‌گذاری به‌عنوان خوانده‌شده در نظر گرفته‌شوند.
settings.appearance=ظاهر 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.theme=پوسته
settings.submit_your_theme=پوستهٔ خود را ارسال‌کنید settings.submit_your_theme=پوستهٔ خود را ارسال‌کنید
settings.custom_css=سی‌اس‌اس شخصی‌سازی‌شده settings.custom_css=سی‌اس‌اس شخصی‌سازی‌شده
@@ -85,6 +88,7 @@ details.feed_url=نشانی خوراک
details.generate_api_key_first=ابتدا یک کلید API در نمایهٔ خود ایجاد کنید. details.generate_api_key_first=ابتدا یک کلید API در نمایهٔ خود ایجاد کنید.
details.unsubscribe=لغو اشتراک details.unsubscribe=لغو اشتراک
details.category_details=جزئیات دسته details.category_details=جزئیات دسته
details.tag_details=Tag details ####### Needs translation
details.parent_category=ردهٔ پدر details.parent_category=ردهٔ پدر
profile.user_name=نام کاربری profile.user_name=نام کاربری

View File

@@ -6,6 +6,7 @@ global.download=Lataa
global.link=Linkki global.link=Linkki
global.bookmark=Kirjanmerkki global.bookmark=Kirjanmerkki
global.close=Sulje global.close=Sulje
global.tags=Tags ####### Needs translation
tree.subscribe=Tilaa syöte tree.subscribe=Tilaa syöte
tree.import=Tuo 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.social_buttons=Näytä jakonapit
settings.general.scroll_marks=Laajennetussa näkymässä otsikoiden selaaminen merkitsee ne luetuiksi settings.general.scroll_marks=Laajennetussa näkymässä otsikoiden selaaminen merkitsee ne luetuiksi
settings.appearance=Ulkonäkö 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.theme=Teema
settings.submit_your_theme=Lähetä oma teemasi settings.submit_your_theme=Lähetä oma teemasi
settings.custom_css=Oma CSS 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.generate_api_key_first=Luo API-avain profiilissasi.
details.unsubscribe=Peruuta tilaus details.unsubscribe=Peruuta tilaus
details.category_details=Kansion tiedot details.category_details=Kansion tiedot
details.tag_details=Tag details ####### Needs translation
details.parent_category=Yläkansio details.parent_category=Yläkansio
profile.user_name=Käyttäjänimi profile.user_name=Käyttäjänimi

View File

@@ -6,6 +6,7 @@ global.download=Télécharger
global.link=Lien global.link=Lien
global.bookmark=Favoris global.bookmark=Favoris
global.close=Fermer global.close=Fermer
global.tags=Tags ####### Needs translation
tree.subscribe=S'abonner tree.subscribe=S'abonner
tree.import=Importer 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.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.general.scroll_marks=En mode de lecture étendu, marquer comme lu les éléments lorsque la fenêtre descend.
settings.appearance=Apparence 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.theme=Thème
settings.submit_your_theme=Soumettez votre thème. settings.submit_your_theme=Soumettez votre thème.
settings.custom_css=CSS personnelle 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.generate_api_key_first=Générez une clé API dans votre profil d'abord.
details.unsubscribe=Se désabonner details.unsubscribe=Se désabonner
details.category_details=Détails de la catégorie details.category_details=Détails de la catégorie
details.tag_details=Tag details ####### Needs translation
details.parent_category=Catégorie parente details.parent_category=Catégorie parente
profile.user_name=Nom profile.user_name=Nom

View File

@@ -6,6 +6,7 @@ global.download=Descargar
global.link=Ligazón global.link=Ligazón
global.bookmark=Marcador global.bookmark=Marcador
global.close=Pechar global.close=Pechar
global.tags=Tags ####### Needs translation
tree.subscribe=Subscribir tree.subscribe=Subscribir
tree.import=Importar 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.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.general.scroll_marks=En vista expandida, o desplazamento polas entradas márcaas como lidas.
settings.appearance=Aspecto 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.theme=Decorado
settings.submit_your_theme=Envíe o seu decorado settings.submit_your_theme=Envíe o seu decorado
settings.custom_css=CSS Personalizado 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.generate_api_key_first=Antes debes xerar unha chave API no teu perfil.
details.unsubscribe=Rematar suscripción details.unsubscribe=Rematar suscripción
details.category_details=Detalles da categoría details.category_details=Detalles da categoría
details.tag_details=Tag details ####### Needs translation
details.parent_category=Categoría principal details.parent_category=Categoría principal
profile.user_name=Nome de usuario profile.user_name=Nome de usuario

View File

@@ -6,6 +6,7 @@ global.download=جیرأکش
global.link=خال global.link=خال
global.bookmark=بوکمارک global.bookmark=بوکمارک
global.close=دَوَستن global.close=دَوَستن
global.tags=Tags ####### Needs translation
tree.subscribe=مشترک ببید tree.subscribe=مشترک ببید
tree.import=درینأدأن tree.import=درینأدأن
@@ -67,6 +68,8 @@ settings.general.show_unread=تنها خوراک‌ها و دسته‌های ر
settings.general.social_buttons=نشان‌دادن دکمه‌های اشتراک‌گذاری در شبکه‌های اجتماعی settings.general.social_buttons=نشان‌دادن دکمه‌های اشتراک‌گذاری در شبکه‌های اجتماعی
settings.general.scroll_marks=در نمای گسترش‌یافته، لغزیدن بر روی مطالب به‌عنوان نشانه‌گذاری به‌عنوان خوانده‌شده در نظر گرفته‌شوند. settings.general.scroll_marks=در نمای گسترش‌یافته، لغزیدن بر روی مطالب به‌عنوان نشانه‌گذاری به‌عنوان خوانده‌شده در نظر گرفته‌شوند.
settings.appearance=ظاهر 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.theme=پوسته
settings.submit_your_theme=شیمه پوستهٰ اوسه کونید settings.submit_your_theme=شیمه پوستهٰ اوسه کونید
settings.custom_css=سی‌اس‌اس شخصی‌سازی‌شده settings.custom_css=سی‌اس‌اس شخصی‌سازی‌شده
@@ -85,6 +88,7 @@ details.feed_url=نشانی خوراک
details.generate_api_key_first=ابتدا یک کلید API در نمایهٔ خود ایجاد کنید. details.generate_api_key_first=ابتدا یک کلید API در نمایهٔ خود ایجاد کنید.
details.unsubscribe=لغو اشتراک details.unsubscribe=لغو اشتراک
details.category_details=جرگه جزئیات details.category_details=جرگه جزئیات
details.tag_details=Tag details ####### Needs translation
details.parent_category=پئرˇ جرگه details.parent_category=پئرˇ جرگه
profile.user_name=کاربری نام profile.user_name=کاربری نام

View File

@@ -6,6 +6,7 @@ global.download=Letöltés
global.link=Link global.link=Link
global.bookmark=Könyvjelző global.bookmark=Könyvjelző
global.close=Bezár global.close=Bezár
global.tags=Tags ####### Needs translation
tree.subscribe=Feliratkozás tree.subscribe=Feliratkozás
tree.import=Importálá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.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.general.scroll_marks=Kiterjesztett nézetben, görgetéssel olvasottként jelöli meg a bejegyzést
settings.appearance=Megjelenés 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.theme=Téma
settings.submit_your_theme=Küldje el a témáját settings.submit_your_theme=Küldje el a témáját
settings.custom_css=Saját CSS 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.generate_api_key_first=A profiljában először egy API kulcsot kell generálnia.
details.unsubscribe=Leiratkozás details.unsubscribe=Leiratkozás
details.category_details=Kategória részletei details.category_details=Kategória részletei
details.tag_details=Tag details ####### Needs translation
details.parent_category=Szülő kategória details.parent_category=Szülő kategória
profile.user_name=Felhasználói név profile.user_name=Felhasználói név

View File

@@ -6,6 +6,7 @@ global.download=Download
global.link=Link global.link=Link
global.bookmark=Segnalibro global.bookmark=Segnalibro
global.close=Chiudi global.close=Chiudi
global.tags=Tags ####### Needs translation
tree.subscribe=Iscriviti tree.subscribe=Iscriviti
tree.import=Importa 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.social_buttons=Visualizza i social button
settings.general.scroll_marks=Marca come letto quando scorri settings.general.scroll_marks=Marca come letto quando scorri
settings.appearance=Appearance ####### Needs translation 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.theme=Tema
settings.submit_your_theme=Sottoponi il tuo tema settings.submit_your_theme=Sottoponi il tuo tema
settings.custom_css=Css modificato 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.generate_api_key_first=Generate an API key in your profile first.
details.unsubscribe=Annulla l"'"iscrizione details.unsubscribe=Annulla l"'"iscrizione
details.category_details=Dettagli categoria details.category_details=Dettagli categoria
details.tag_details=Tag details ####### Needs translation
details.parent_category=Parent category details.parent_category=Parent category
profile.user_name=User name profile.user_name=User name

View File

@@ -6,6 +6,7 @@ global.download=Download ####### Needs translation
global.link=Link ####### Needs translation global.link=Link ####### Needs translation
global.bookmark=Bookmark ####### Needs translation global.bookmark=Bookmark ####### Needs translation
global.close=Close ####### Needs translation global.close=Close ####### Needs translation
global.tags=Tags ####### Needs translation
tree.subscribe=구독 tree.subscribe=구독
tree.import=임포트 tree.import=임포트
@@ -67,6 +68,8 @@ settings.general.show_unread=안읽은 항목들이 있는 피드와 카테고
settings.general.social_buttons=소셜미디아 버튼들 보여주기 settings.general.social_buttons=소셜미디아 버튼들 보여주기
settings.general.scroll_marks=Expanded View에서 스크롤하면 항목들을 읽음으로 저장하기 settings.general.scroll_marks=Expanded View에서 스크롤하면 항목들을 읽음으로 저장하기
settings.appearance=Appearance ####### Needs translation 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.theme=Theme ####### Needs translation
settings.submit_your_theme=Submit your theme ####### Needs translation settings.submit_your_theme=Submit your theme ####### Needs translation
settings.custom_css=커스톰 CSS settings.custom_css=커스톰 CSS
@@ -85,6 +88,7 @@ details.feed_url=피드 유알엘
details.generate_api_key_first=당신의 프로필을 위해 API Key를 먼저 생성하세요. details.generate_api_key_first=당신의 프로필을 위해 API Key를 먼저 생성하세요.
details.unsubscribe=주소 삭제 details.unsubscribe=주소 삭제
details.category_details=카테고리 세부 details.category_details=카테고리 세부
details.tag_details=Tag details ####### Needs translation
details.parent_category=부모 카테고리 details.parent_category=부모 카테고리
profile.user_name=사용자 이름 profile.user_name=사용자 이름

View File

@@ -6,6 +6,7 @@ global.download=Muat turun
global.link=Pautan global.link=Pautan
global.bookmark=Bookmark global.bookmark=Bookmark
global.close=Tutup global.close=Tutup
global.tags=Tags ####### Needs translation
tree.subscribe=Langgan tree.subscribe=Langgan
tree.import=Import 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.social_buttons=Tunjuk social sharing
settings.general.scroll_marks=Dalam wide view, tanda item dibaca ketika scrolling settings.general.scroll_marks=Dalam wide view, tanda item dibaca ketika scrolling
settings.appearance=Rupa 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.theme=Tema
settings.submit_your_theme=Muat naik tema anda settings.submit_your_theme=Muat naik tema anda
settings.custom_css=Custom CSS 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.generate_api_key_first=Janakan API key dalam profil anda dahulu.
details.unsubscribe=Hentikan langganan details.unsubscribe=Hentikan langganan
details.category_details=Butir-butir kategori details.category_details=Butir-butir kategori
details.tag_details=Tag details ####### Needs translation
details.parent_category=Kategori induk details.parent_category=Kategori induk
profile.user_name=User name profile.user_name=User name

View File

@@ -6,6 +6,7 @@ global.download=Last ned
global.link=Lenke global.link=Lenke
global.bookmark=Bokmerke global.bookmark=Bokmerke
global.close=Lukk global.close=Lukk
global.tags=Tags ####### Needs translation
tree.subscribe=Abonner tree.subscribe=Abonner
tree.import=Importer 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.social_buttons=Vis delingsknapper
settings.general.scroll_marks=I utvidet visning, merk artikler som leste når du blar deg forbi dem. settings.general.scroll_marks=I utvidet visning, merk artikler som leste når du blar deg forbi dem.
settings.appearance=Utseende 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.theme=Drakt
settings.submit_your_theme=Legg til egen drakt settings.submit_your_theme=Legg til egen drakt
settings.custom_css=Egendefinert CSS 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.generate_api_key_first=Generer en API-nøkkel under profilinnstillinger først.
details.unsubscribe=Avslutt abonnement details.unsubscribe=Avslutt abonnement
details.category_details=Kategoridetaljer details.category_details=Kategoridetaljer
details.tag_details=Tag details ####### Needs translation
details.parent_category=Overordnet kategori details.parent_category=Overordnet kategori
profile.user_name=Brukernavn profile.user_name=Brukernavn

View File

@@ -6,6 +6,7 @@ global.download=Download
global.link=Link global.link=Link
global.bookmark=Bookmark global.bookmark=Bookmark
global.close=Sluiten global.close=Sluiten
global.tags=Tags ####### Needs translation
tree.subscribe=Abonneer tree.subscribe=Abonneer
tree.import=Importeer 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.social_buttons=Laat Social Media knoppen zien
settings.general.scroll_marks=Markeer artikelen als gelezen, wanneer je er doorheen scrollt settings.general.scroll_marks=Markeer artikelen als gelezen, wanneer je er doorheen scrollt
settings.appearance=Uiterlijk 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.theme=Thema
settings.submit_your_theme=Stuur thema in settings.submit_your_theme=Stuur thema in
settings.custom_css=Custom CSS 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.generate_api_key_first=Genereer eerst een API sleutel in je profiel.
details.unsubscribe=Abonnement opzeggen details.unsubscribe=Abonnement opzeggen
details.category_details=Categorie details details.category_details=Categorie details
details.tag_details=Tag details ####### Needs translation
details.parent_category=Bovenliggende categorie details.parent_category=Bovenliggende categorie
profile.user_name=Gebruikersnaam profile.user_name=Gebruikersnaam

View File

@@ -6,6 +6,7 @@ global.download=Last ned
global.link=Lenkje global.link=Lenkje
global.bookmark=Bokmerke global.bookmark=Bokmerke
global.close=Lukk global.close=Lukk
global.tags=Tags ####### Needs translation
tree.subscribe=Abonner tree.subscribe=Abonner
tree.import=Importer 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.social_buttons=Vis delingsknappar
settings.general.scroll_marks=I utvida visning, merk artiklar som lesne når du blar deg forbi dei. settings.general.scroll_marks=I utvida visning, merk artiklar som lesne når du blar deg forbi dei.
settings.appearance=Utsjånad 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.theme=Drakt
settings.submit_your_theme=Legg til eiga drakt settings.submit_your_theme=Legg til eiga drakt
settings.custom_css=Skreddarsydd CSS 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.generate_api_key_first=Generer ein API-nykel under profilinnstillingar fyrst.
details.unsubscribe=Avslutt abonnement details.unsubscribe=Avslutt abonnement
details.category_details=Kategoridetaljar details.category_details=Kategoridetaljar
details.tag_details=Tag details ####### Needs translation
details.parent_category=Overordna kategori details.parent_category=Overordna kategori
profile.user_name=Brukarnamn profile.user_name=Brukarnamn

View File

@@ -6,6 +6,7 @@ global.download=Pobierz
global.link=Odnośnik global.link=Odnośnik
global.bookmark=Zakładka global.bookmark=Zakładka
global.close=Zamknij global.close=Zamknij
global.tags=Tags ####### Needs translation
tree.subscribe=Subskrybuj tree.subscribe=Subskrybuj
tree.import=Importuj 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.social_buttons=Pokaż przyciski udostępniania
settings.general.scroll_marks=W widoku rozwiniętym przewijanie oznacza elementy jako przeczytane settings.general.scroll_marks=W widoku rozwiniętym przewijanie oznacza elementy jako przeczytane
settings.appearance=Wygląd 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.theme=Motyw
settings.submit_your_theme=Wyślij swój motyw settings.submit_your_theme=Wyślij swój motyw
settings.custom_css=Własny styl CSS 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.generate_api_key_first=Najpierw wygeneruj klucz API w swoim profilu.
details.unsubscribe=Cofnij subskrypcje details.unsubscribe=Cofnij subskrypcje
details.category_details=Szczegóły kategorii details.category_details=Szczegóły kategorii
details.tag_details=Tag details ####### Needs translation
details.parent_category=Kategoria nadrzędna details.parent_category=Kategoria nadrzędna
profile.user_name=Nazwa użytkownika profile.user_name=Nazwa użytkownika

View File

@@ -6,6 +6,7 @@ global.download=Download
global.link=Link global.link=Link
global.bookmark=Favorito global.bookmark=Favorito
global.close=Fechar global.close=Fechar
global.tags=Tags ####### Needs translation
tree.subscribe=Inscrever-se tree.subscribe=Inscrever-se
tree.import=Importar 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.social_buttons=Mostrar botões de mídias sociais
settings.general.scroll_marks=No modo expandido, percorrer os itens marca-os como lidos settings.general.scroll_marks=No modo expandido, percorrer os itens marca-os como lidos
settings.appearance=Aparência 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.theme=Tema
settings.submit_your_theme=Envie seu tema settings.submit_your_theme=Envie seu tema
settings.custom_css=CSS personalizado 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.generate_api_key_first=Gerar uma chave de API em seu perfil primeiro.
details.unsubscribe=Cancelar inscrição details.unsubscribe=Cancelar inscrição
details.category_details=Detalhes da categoria details.category_details=Detalhes da categoria
details.tag_details=Tag details ####### Needs translation
details.parent_category=Categoria pai details.parent_category=Categoria pai
profile.user_name=Nome de usuário profile.user_name=Nome de usuário

View File

@@ -6,6 +6,7 @@ global.download=Скачать
global.link=Ссылка global.link=Ссылка
global.bookmark=Закладка global.bookmark=Закладка
global.close=Закрыть global.close=Закрыть
global.tags=Tags ####### Needs translation
tree.subscribe=Подписаться tree.subscribe=Подписаться
tree.import=Импорт tree.import=Импорт
@@ -67,6 +68,8 @@ settings.general.show_unread=Показывать прочтённые лент
settings.general.social_buttons=Показывать социальные кнопки settings.general.social_buttons=Показывать социальные кнопки
settings.general.scroll_marks=В развёрнутом виде помечать записи как прочитанные по мере прокрутки settings.general.scroll_marks=В развёрнутом виде помечать записи как прочитанные по мере прокрутки
settings.appearance=Вид 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.theme=Тема
settings.submit_your_theme=Добавьте свою тему settings.submit_your_theme=Добавьте свою тему
settings.custom_css=Собственная CSS settings.custom_css=Собственная CSS
@@ -85,6 +88,7 @@ details.feed_url=Адрес ленты
details.generate_api_key_first=Сначала сгенерируйте API-ключ в вашем профиле. details.generate_api_key_first=Сначала сгенерируйте API-ключ в вашем профиле.
details.unsubscribe=Отписаться details.unsubscribe=Отписаться
details.category_details=Информация о категории details.category_details=Информация о категории
details.tag_details=Tag details ####### Needs translation
details.parent_category=Родительская категория details.parent_category=Родительская категория
profile.user_name=Имя пользователя profile.user_name=Имя пользователя

View File

@@ -6,6 +6,7 @@ global.download=Stiahnuť
global.link=Link global.link=Link
global.bookmark=Záložky global.bookmark=Záložky
global.close=Zavrieť global.close=Zavrieť
global.tags=Tags ####### Needs translation
tree.subscribe=Odoberať tree.subscribe=Odoberať
tree.import=Importovať 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.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.general.scroll_marks=Scrollovanie v rozšírenom náhľade označí položky ako prečítané
settings.appearance=Vzhľad 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.theme=Motív
settings.submit_your_theme=Nahrať vlastný motív vzhľadu settings.submit_your_theme=Nahrať vlastný motív vzhľadu
settings.custom_css=Vlastný motív vzhľadu (CSS) 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.generate_api_key_first=Vygenerujte si API kľúč vo vašom profile.
details.unsubscribe=Zrušiť odoberanie. details.unsubscribe=Zrušiť odoberanie.
details.category_details=Detaily kategórie details.category_details=Detaily kategórie
details.tag_details=Tag details ####### Needs translation
details.parent_category=Hlavná kategória details.parent_category=Hlavná kategória
profile.user_name=Uživateľské meno profile.user_name=Uživateľské meno

View File

@@ -1,150 +1,154 @@
global.save=Spara global.save=Spara
global.cancel=Avbryt global.cancel=Avbryt
global.delete=Radera global.delete=Radera
global.required=Obligatorisk global.required=Obligatorisk
global.download=Ladda ned global.download=Ladda ned
global.link=Länka global.link=Länka
global.bookmark=Bokmärk global.bookmark=Bokmärk
global.close=Stäng global.close=Stäng
global.tags=Tags ####### Needs translation
tree.subscribe=Prenumerera
tree.import=Importera tree.subscribe=Prenumerera
tree.new_category=Ny kategori tree.import=Importera
tree.all=Alla tree.new_category=Ny kategori
tree.starred=Stjärnmärkt tree.all=Alla
tree.starred=Stjärnmärkt
subscribe.feed_url=Prenumerationens URL
subscribe.feed_name=Prenumerationens namn subscribe.feed_url=Prenumerationens URL
subscribe.category=Kategori 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_reader_prefix=Låt mig importera dina prenumerationer från ditt
import.google_download=Alternativt, ladda upp din subscriptions.xml-fil. import.google_reader_suffix=-konto.
import.google_download_link=Ladda ned den här. import.google_download=Alternativt, ladda upp din subscriptions.xml-fil.
import.xml_file=OPML-fil import.google_download_link=Ladda ned den här.
import.xml_file=OPML-fil
new_category.name=Namn
new_category.parent=Överordnad new_category.name=Namn
new_category.parent=Överordnad
toolbar.unread=Oläst
toolbar.all=Alla toolbar.unread=Oläst
toolbar.previous_entry=Föregående post toolbar.all=Alla
toolbar.next_entry=Nästa post toolbar.previous_entry=Föregående post
toolbar.refresh=Uppdatera toolbar.next_entry=Nästa post
toolbar.refresh_all=Tvinga uppdatering av alla prenumerationer toolbar.refresh=Uppdatera
toolbar.sort_by_asc_desc=Sortera efter datum stigande/fallande toolbar.refresh_all=Tvinga uppdatering av alla prenumerationer
toolbar.titles_only=Endast titlar toolbar.sort_by_asc_desc=Sortera efter datum stigande/fallande
toolbar.expanded_view=Expanderad vy toolbar.titles_only=Endast titlar
toolbar.mark_all_as_read=Markera alla som lästa toolbar.expanded_view=Expanderad vy
toolbar.mark_all_older_12_hours=Poster äldre än 12 timmar toolbar.mark_all_as_read=Markera alla som lästa
toolbar.mark_all_older_day=Poster äldre än en dag toolbar.mark_all_older_12_hours=Poster äldre än 12 timmar
toolbar.mark_all_older_week=Poster äldre än en vecka toolbar.mark_all_older_day=Poster äldre än en dag
toolbar.mark_all_older_two_weeks=Poster äldre än två veckor toolbar.mark_all_older_week=Poster äldre än en vecka
toolbar.settings=Inställningar toolbar.mark_all_older_two_weeks=Poster äldre än två veckor
toolbar.profile=Profil toolbar.settings=Inställningar
toolbar.admin=Administratör toolbar.profile=Profil
toolbar.about=Om toolbar.admin=Administratör
toolbar.logout=Logga ut toolbar.about=Om
toolbar.donate=Donera toolbar.logout=Logga ut
toolbar.donate=Donera
view.entry_source=från
view.entry_author=av view.entry_source=från
view.error_while_loading_feed=Fel under laddning av denna prenumeration view.entry_author=av
view.keep_unread=Håll oläst view.error_while_loading_feed=Fel under laddning av denna prenumeration
view.no_unread_items=har inga olästa poster. view.keep_unread=Håll oläst
view.mark_up_to_here=Markera som läst upp till denna post view.no_unread_items=har inga olästa poster.
view.search_for=söker efter: view.mark_up_to_here=Markera som läst upp till denna post
view.no_search_results=Inga resultat för valda nyckelord 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.hint=Skriv in en prenumeration...
feedsearch.result_prefix=Dina prenumerationer: 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=Allmänt
settings.general.language.contribute=Bidra med översättningar settings.general.language=Språk
settings.general.show_unread=Visa prenumerationer och kategorier utan olästa poster settings.general.language.contribute=Bidra med översättningar
settings.general.social_buttons=Visa delningsknappar settings.general.show_unread=Visa prenumerationer och kategorier utan olästa poster
settings.general.scroll_marks=I expanderad vy, markera poster som lästa genom att scrolla förbi dem settings.general.social_buttons=Visa delningsknappar
settings.appearance=Utseende settings.general.scroll_marks=I expanderad vy, markera poster som lästa genom att scrolla förbi dem
settings.theme=Tema settings.appearance=Utseende
settings.submit_your_theme=Skicka in ditt tema settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.custom_css=Anpassad CSS settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=Tema
details.feed_details=Prenumerationsdetaljer settings.submit_your_theme=Skicka in ditt tema
details.url=URL settings.custom_css=Anpassad CSS
details.website=Webbsida
details.name=Namn details.feed_details=Prenumerationsdetaljer
details.category=Kategori details.url=URL
details.position=Position details.website=Webbsida
details.last_refresh=Senaste uppdatering details.name=Namn
details.message=Senaste uppdateringsmeddelande details.category=Kategori
details.next_refresh=Nästa uppdatering details.position=Position
details.queued_for_refresh=I kö för uppdatering details.last_refresh=Senaste uppdatering
details.feed_url=Prenumerationens URL details.message=Senaste uppdateringsmeddelande
details.generate_api_key_first=Skapa en API-nyckel på din profil först. details.next_refresh=Nästa uppdatering
details.unsubscribe=Avprenumerera details.queued_for_refresh=I kö för uppdatering
details.category_details=Kategoridetaljer details.feed_url=Prenumerationens URL
details.parent_category=Överordnad kategori details.generate_api_key_first=Skapa en API-nyckel på din profil först.
details.unsubscribe=Avprenumerera
profile.user_name=Användarnamn details.category_details=Kategoridetaljer
profile.email=E-mail details.tag_details=Tag details ####### Needs translation
profile.change_password=Ändra lösenord details.parent_category=Överordnad kategori
profile.confirm_password=Bekräfta lösenord
profile.minimum_6_chars=Minst 6 bokstäver profile.user_name=Användarnamn
profile.passwords_do_not_match=Lösenorden matchar inte profile.email=E-mail
profile.api_key=API-nyckel profile.change_password=Ändra lösenord
profile.api_key_not_generated=Inte skapad än profile.confirm_password=Bekräfta lösenord
profile.generate_new_api_key=Skapa ny API-nyckel profile.minimum_6_chars=Minst 6 bokstäver
profile.generate_new_api_key_info=Lösenordsbyte skapar ny API-nyckel profile.passwords_do_not_match=Lösenorden matchar inte
profile.opml_export=OPML-export profile.api_key=API-nyckel
profile.delete_account=Radera konto profile.api_key_not_generated=Inte skapad än
profile.generate_new_api_key=Skapa ny API-nyckel
about.rest_api=REST-API profile.generate_new_api_key_info=Lösenordsbyte skapar ny API-nyckel
about.keyboard_shortcuts=Tangentbordsgenvägar profile.opml_export=OPML-export
about.version=CommaFeed-version profile.delete_account=Radera konto
about.line1_prefix=CommaFeed är ett open-source-projekt. Källan är tillgänglig på
about.line1_suffix=. about.rest_api=REST-API
about.line2_prefix=Om du träffar på ett problem, meddela det på "Issues"-sidan för about.keyboard_shortcuts=Tangentbordsgenvägar
about.line2_suffix=-projektet. about.version=CommaFeed-version
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.line1_prefix=CommaFeed är ett open-source-projekt. Källan är tillgänglig på
about.line4=För er som föredrar Bitcoin, här är adressen about.line1_suffix=.
about.goodies=Godsaker about.line2_prefix=Om du träffar på ett problem, meddela det på "Issues"-sidan för
about.goodies.android_app=Android-app about.line2_suffix=-projektet.
about.goodies.subscribe_url=Prenumerations-URL 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.goodies.chrome_extension=Chrome-tillägg about.line4=För er som föredrar Bitcoin, här är adressen
about.goodies.firefox_extension=Firefox-tillägg about.goodies=Godsaker
about.goodies.opera_extension=Opera-tillägg about.goodies.android_app=Android-app
about.goodies.subscribe_bookmarklet=Bokmärke för tillägg av prenumeration (klicka) about.goodies.subscribe_url=Prenumerations-URL
about.goodies.subscribe_bookmarklet_asc=äldst först about.goodies.chrome_extension=Chrome-tillägg
about.goodies.subscribe_bookmarklet_desc=nyast först about.goodies.firefox_extension=Firefox-tillägg
about.goodies.next_unread_bookmarklet=Bokmärke för nästa olästa post (dra till bokmärkesfält) about.goodies.opera_extension=Opera-tillägg
about.translation=Översättning about.goodies.subscribe_bookmarklet=Bokmärke för tillägg av prenumeration (klicka)
about.translation.message=Vi behöver din hjälp med att översätta CommaFeed. about.goodies.subscribe_bookmarklet_asc=äldst först
about.translation.link=Se hur du kan bidra med översättningar. about.goodies.subscribe_bookmarklet_desc=nyast först
about.announcements=Notiser about.goodies.next_unread_bookmarklet=Bokmärke för nästa olästa post (dra till bokmärkesfält)
about.rest_api.line1=CommaFeed är byggt på JAX-RS och AngularJS. Tack vare detta är en REST-API tillgänglig. about.translation=Översättning
about.rest_api.link_to_documentation=Länk till dokumentation. 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.shortcuts.mouse_middleclick=mitten-musknapp about.announcements=Notiser
about.shortcuts.open_next_entry=öppna nästa post about.rest_api.line1=CommaFeed är byggt på JAX-RS och AngularJS. Tack vare detta är en REST-API tillgänglig.
about.shortcuts.open_previous_entry=öppna föregående post about.rest_api.link_to_documentation=Länk till dokumentation.
about.shortcuts.spacebar=mellanslag/shift+mellanslag
about.shortcuts.move_page_down_up=flyttar sidan ned/upp about.shortcuts.mouse_middleclick=mitten-musknapp
about.shortcuts.focus_next_entry=sätt fokus på nästa post utan att öppna about.shortcuts.open_next_entry=öppna nästa post
about.shortcuts.focus_previous_entry=sätt fokus på föregående post utan att öppna about.shortcuts.open_previous_entry=öppna föregående post
about.shortcuts.open_next_feed=öppna nästa prenumeration eller kategori about.shortcuts.spacebar=mellanslag/shift+mellanslag
about.shortcuts.open_previous_feed=öppna föregående prenumeration eller kategori about.shortcuts.move_page_down_up=flyttar sidan ned/upp
about.shortcuts.open_close_current_entry=öppna/stäng nuvarande post about.shortcuts.focus_next_entry=sätt fokus på nästa post utan att öppna
about.shortcuts.open_current_entry_in_new_window=öppna nuvarande post i nytt fönster about.shortcuts.focus_previous_entry=sätt fokus på föregående post utan att öppna
about.shortcuts.open_current_entry_in_new_window_background=öppna nuvarande post i nytt bakgrundsfönster about.shortcuts.open_next_feed=öppna nästa prenumeration eller kategori
about.shortcuts.star_unstar=stjärnmärk/ostjärnmärk nuvarande post about.shortcuts.open_previous_feed=öppna föregående prenumeration eller kategori
about.shortcuts.mark_current_entry=markera nuvarande post läst/oläst about.shortcuts.open_close_current_entry=öppna/stäng nuvarande post
about.shortcuts.mark_all_as_read=markera alla som lästa about.shortcuts.open_current_entry_in_new_window=öppna nuvarande post i nytt fönster
about.shortcuts.open_in_new_tab_mark_as_read=öppna nuvarande post i ny flik och markera som läst about.shortcuts.open_current_entry_in_new_window_background=öppna nuvarande post i nytt bakgrundsfönster
about.shortcuts.fullscreen=växla till/från fullskärmsläge about.shortcuts.star_unstar=stjärnmärk/ostjärnmärk nuvarande post
about.shortcuts.font_size=öka/minska teckenstorlek av nuvarande post about.shortcuts.mark_current_entry=markera nuvarande post läst/oläst
about.shortcuts.go_to_all=se alla poster about.shortcuts.mark_all_as_read=markera alla som lästa
about.shortcuts.go_to_starred=se stjärnmärkta poster about.shortcuts.open_in_new_tab_mark_as_read=öppna nuvarande post i ny flik och markera som läst
about.shortcuts.feed_search=navigera till en prenumeration via prenumerationsnamn 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

View File

@@ -6,6 +6,7 @@ global.download=İndir
global.link=Bağlantı global.link=Bağlantı
global.bookmark=Yer imi global.bookmark=Yer imi
global.close=Kapat global.close=Kapat
global.tags=Tags ####### Needs translation
tree.subscribe=Abone ol tree.subscribe=Abone ol
tree.import=İçe aktar 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.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.general.scroll_marks=Genişletilmiş görünümde götüntülenen iletileri okunmuş işaretle
settings.appearance=Görünüm 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.theme=Tema
settings.submit_your_theme=Tema gönder settings.submit_your_theme=Tema gönder
settings.custom_css=Kişiselleştirilmiş CSS 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.generate_api_key_first=Öncelikle profilinizden bir API anahtarı oluşturun.
details.unsubscribe=Aboneliği iptal et details.unsubscribe=Aboneliği iptal et
details.category_details=Kategori detayları details.category_details=Kategori detayları
details.tag_details=Tag details ####### Needs translation
details.parent_category=Üst kategori details.parent_category=Üst kategori
profile.user_name=Kullanıcı adı profile.user_name=Kullanıcı adı

View File

@@ -6,6 +6,7 @@ global.download=下载
global.link=链接 global.link=链接
global.bookmark=书签 global.bookmark=书签
global.close=关闭 global.close=关闭
global.tags=Tags ####### Needs translation
tree.subscribe=订阅 tree.subscribe=订阅
tree.import=导入 tree.import=导入
@@ -67,6 +68,8 @@ settings.general.show_unread=显示未读的订阅和目录条目
settings.general.social_buttons=显示分享按钮 settings.general.social_buttons=显示分享按钮
settings.general.scroll_marks=在扩展视图中,可滚动条目将其标记为已读 settings.general.scroll_marks=在扩展视图中,可滚动条目将其标记为已读
settings.appearance=外观 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.theme=主题
settings.submit_your_theme=提交你的主题 settings.submit_your_theme=提交你的主题
settings.custom_css=自定义 CSS 样式 settings.custom_css=自定义 CSS 样式
@@ -85,6 +88,7 @@ details.feed_url=订阅地址
details.generate_api_key_first=在您的配置文件中首先生成一个 API 密钥。 details.generate_api_key_first=在您的配置文件中首先生成一个 API 密钥。
details.unsubscribe=取消订阅 details.unsubscribe=取消订阅
details.category_details=目录详情 details.category_details=目录详情
details.tag_details=Tag details ####### Needs translation
details.parent_category=上一层目录 details.parent_category=上一层目录
profile.user_name=用户名 profile.user_name=用户名

View File

@@ -125,17 +125,40 @@ module.controller('SubscribeCtrl', ['$scope', 'FeedService', 'CategoryService',
}]); }]);
module.controller('CategoryTreeCtrl', ['$scope', '$timeout', '$stateParams', '$window', '$location', '$state', '$route', 'CategoryService', module.controller('CategoryTreeCtrl', ['$scope', '$timeout', '$stateParams', '$window', '$location', '$state', '$route', 'CategoryService',
'AnalyticsService', 'AnalyticsService', 'EntryService',
function($scope, $timeout, $stateParams, $window, $location, $state, $route, CategoryService, AnalyticsService) { function($scope, $timeout, $stateParams, $window, $location, $state, $route, CategoryService, AnalyticsService, EntryService) {
$scope.selectedType = $stateParams._type; $scope.selectedType = $stateParams._type;
$scope.selectedId = $stateParams._id; $scope.selectedId = $stateParams._id;
$scope.EntryService = EntryService;
$scope.starred = { $scope.starred = {
id : 'starred', id : 'starred',
name : '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.$on('$stateChangeSuccess', function() {
$scope.selectedType = $stateParams._type; $scope.selectedType = $stateParams._type;
$scope.selectedId = $stateParams._id; $scope.selectedId = $stateParams._id;
@@ -201,7 +224,7 @@ module.controller('CategoryTreeCtrl', ['$scope', '$timeout', '$stateParams', '$w
var getCurrentIndex = function(id, type, flat) { var getCurrentIndex = function(id, type, flat) {
var index = -1; var index = -1;
for ( var i = 0; i < flat.length; i++) { for (var i = 0; i < flat.length; i++) {
var node = flat[i]; var node = flat[i];
if (node[0] == id && node[1] == type) { if (node[0] == id && node[1] == type) {
index = i; index = i;
@@ -355,7 +378,7 @@ module.controller('CategoryDetailsCtrl', ['$scope', '$state', '$stateParams', 'F
}; };
return; return;
} }
for ( var i = 0; i < CategoryService.flatCategories.length; i++) { for (var i = 0; i < CategoryService.flatCategories.length; i++) {
var cat = CategoryService.flatCategories[i]; var cat = CategoryService.flatCategories[i];
if (cat.id == $stateParams._id) { if (cat.id == $stateParams._id) {
$scope.category = { $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', [ module.controller('ToolbarCtrl', [
'$scope', '$scope',
'$http', '$http',
@@ -501,7 +583,7 @@ module.controller('ToolbarCtrl', [
$scope.markAllAsRead = function() { $scope.markAllAsRead = function() {
markAll(); markAll();
}; };
$scope.markAll12Hours = function() { $scope.markAll12Hours = function() {
markAll(new Date().getTime() - 43200000); markAll(new Date().getTime() - 43200000);
}; };
@@ -574,7 +656,7 @@ module.controller('FeedSearchCtrl', ['$scope', '$state', '$filter', '$timeout',
} }
var filtered = $scope.filtered; 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) { if ($scope.focus.id == filtered[i].id) {
index = i; index = i;
break; 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.limit = SettingsService.settings.viewMode == 'title' ? 10 : 5;
$scope.busy = false; $scope.busy = false;
$scope.hasMore = true; $scope.hasMore = true;
@@ -733,7 +821,7 @@ module.controller('FeedListCtrl', [
} }
var callback = function(data) { 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]; var entry = data.entries[i];
if (!_.some($scope.entries, { if (!_.some($scope.entries, {
id : entry.id id : entry.id
@@ -750,15 +838,23 @@ module.controller('FeedListCtrl', [
$scope.feedLink = data.feedLink; $scope.feedLink = data.feedLink;
}; };
var service = $scope.selectedType == 'feed' ? FeedService : CategoryService; var data = {
service.entries({
id : $scope.selectedId, id : $scope.selectedId,
readType : $scope.keywords ? 'all' : $scope.settingsService.settings.readingMode, readType : $scope.keywords ? 'all' : $scope.settingsService.settings.readingMode,
order : $scope.settingsService.settings.readingOrder, order : $scope.settingsService.settings.readingOrder,
offset : offset, offset : offset,
limit : limit, limit : limit,
keywords : $scope.keywords 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; var watch_scrolling = true;
@@ -808,7 +904,7 @@ module.controller('FeedListCtrl', [
var docTop = w.scrollTop(); var docTop = w.scrollTop();
var current = null; 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 entry = $scope.entries[i];
var e = $('#entry_' + entry.id); var e = $('#entry_' + entry.id);
if (e.offset().top + e.height() > docTop + $('#toolbar').outerHeight()) { if (e.offset().top + e.height() > docTop + $('#toolbar').outerHeight()) {
@@ -871,7 +967,7 @@ module.controller('FeedListCtrl', [
$scope.markUpTo = function(entry) { $scope.markUpTo = function(entry) {
var entries = []; 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]; var e = $scope.entries[i];
if (!e.read) { if (!e.read) {
entries.push({ entries.push({
@@ -910,7 +1006,7 @@ module.controller('FeedListCtrl', [
var getCurrentIndex = function() { var getCurrentIndex = function() {
var index = -1; var index = -1;
if ($scope.current) { 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]) { if ($scope.current == $scope.entries[i]) {
index = i; index = i;
break; break;
@@ -1351,7 +1447,7 @@ module.controller('ManageDuplicateFeedsCtrl', ['$scope', 'AdminCleanupService',
var callback = function() { var callback = function() {
alert('done!'); 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]; var count = $scope.counts[i];
if (count.autoMerge) { if (count.autoMerge) {
AdminCleanupService.mergeFeeds({ AdminCleanupService.mergeFeeds({

View File

@@ -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 * Reusable favicon component
*/ */
@@ -116,7 +148,8 @@ module.directive('category', [function() {
selectedId : '=', selectedId : '=',
showLabel : '=', showLabel : '=',
showChildren : '=', showChildren : '=',
unreadCount : '&' unreadCount : '&',
tag : '='
}, },
restrict : 'E', restrict : 'E',
replace : true, replace : true,
@@ -174,13 +207,14 @@ module.directive('category', [function() {
} }
}; };
$scope.categoryClicked = function(id) { $scope.categoryClicked = function(id, isTag) {
MobileService.toggleLeftMenu(); MobileService.toggleLeftMenu();
if ($scope.selectedType == 'category' && id == $scope.selectedId) { var type = isTag ? 'tag' : 'category';
if ($scope.selectedType == type && id == $scope.selectedId) {
$scope.$emit('emitReload'); $scope.$emit('emitReload');
} else { } else {
$state.transitionTo('feeds.view', { $state.transitionTo('feeds.view', {
_type : 'category', _type : type,
_id : id _id : id
}); });
} }
@@ -192,10 +226,16 @@ module.directive('category', [function() {
}); });
}; };
$scope.showCategoryDetails = function(category) { $scope.showCategoryDetails = function(id, isTag) {
$state.transitionTo('feeds.category_details', { if (isTag) {
_id : category.id $state.transitionTo('feeds.tag_details', {
}); _id : id
});
} else {
$state.transitionTo('feeds.category_details', {
_id : id
});
}
}; };
$scope.toggleCategory = function(category, event) { $scope.toggleCategory = function(category, event) {

View File

@@ -54,6 +54,11 @@ app.config(['$routeProvider', '$stateProvider', '$urlRouterProvider', '$httpProv
templateUrl : 'templates/feeds.category_details.html', templateUrl : 'templates/feeds.category_details.html',
controller : 'CategoryDetailsCtrl' controller : 'CategoryDetailsCtrl'
}); });
$stateProvider.state('feeds.tag_details', {
url : '/details/tag/:_id',
templateUrl : 'templates/feeds.tag_details.html',
controller : 'TagDetailsCtrl'
});
$stateProvider.state('feeds.help', { $stateProvider.state('feeds.help', {
url : '/help', url : '/help',
templateUrl : 'templates/feeds.help.html', templateUrl : 'templates/feeds.help.html',
@@ -105,7 +110,7 @@ app.config(['$routeProvider', '$stateProvider', '$urlRouterProvider', '$httpProv
templateUrl : 'templates/admin.metrics.html', templateUrl : 'templates/admin.metrics.html',
controller : 'MetricsCtrl' controller : 'MetricsCtrl'
}); });
$urlRouterProvider.when('/', '/feeds/view/category/all'); $urlRouterProvider.when('/', '/feeds/view/category/all');
$urlRouterProvider.when('/admin', '/admin/settings'); $urlRouterProvider.when('/admin', '/admin/settings');
$urlRouterProvider.otherwise('/'); $urlRouterProvider.otherwise('/');

View File

@@ -126,7 +126,7 @@ module.factory('CategoryService', ['$resource', '$http', function($resource, $ht
callback(category, parentName); callback(category, parentName);
var children = category.children; var children = category.children;
if (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); traverse(callback, children[c], category.name);
} }
} }
@@ -271,9 +271,29 @@ module.factory('EntryService', ['$resource', '$http', function($resource, $http)
params : { params : {
_method : 'star' _method : 'star'
} }
},
tag : {
method : 'POST',
params : {
_method : 'tag'
}
} }
}; };
var res = $resource('rest/entry/:_method', {}, actions); 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; return res;
}]); }]);
@@ -296,7 +316,6 @@ module.factory('AdminMetricsService', ['$resource', function($resource) {
return res; return res;
}]); }]);
module.factory('AdminCleanupService', ['$resource', function($resource) { module.factory('AdminCleanupService', ['$resource', function($resource) {
var actions = { var actions = {
findDuplicateFeeds : { findDuplicateFeeds : {

View File

@@ -143,7 +143,7 @@
} }
.full-screen #feed-accordion .entry-body-content { .full-screen #feed-accordion .entry-body-content {
max-width: 100%; max-width: 100%;
} }
#feed-accordion .entry-enclosure { #feed-accordion .entry-enclosure {
@@ -172,6 +172,24 @@
text-decoration: none; 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 { #feed-accordion .entry-buttons label {
margin-bottom: 0px; margin-bottom: 0px;
font-size: 12px; font-size: 12px;

View File

@@ -30,7 +30,7 @@
} }
.sidebar-nav-fixed:hover { .sidebar-nav-fixed:hover {
overflow-y: auto; overflow-y: auto;
} }
.full-screen .left-menu { .full-screen .left-menu {
@@ -39,6 +39,7 @@
.css-treeview { .css-treeview {
margin-top: 15px; margin-top: 15px;
margin-bottom: 30px;
font-family: inherit; font-family: inherit;
font-size: 13px; font-size: 13px;
white-space: nowrap; white-space: nowrap;

View File

@@ -2,6 +2,15 @@
cursor: pointer; cursor: pointer;
} }
.nolink {
color: inherit;
}
.nolink:hover {
text-decoration: none;
color: inherit;
}
.block { .block {
display: block; display: block;
} }

View File

@@ -40,6 +40,9 @@
right: 35px; right: 35px;
margin-top: 22px; margin-top: 22px;
} }
#feed-accordion .tags-panel {
display: block;
}
body.left-menu-active .left-menu { body.left-menu-active .left-menu {
display: block !important; display: block !important;
width: 100%; width: 100%;
@@ -61,4 +64,4 @@
#uvTab { #uvTab {
display: none; display: none;
} }
} }

View File

@@ -1,14 +1,14 @@
<li> <li>
<div class="pointer tree-item" ng-if="showLabel" ng-class="getClass(level - 1)" droppable="node"> <div class="pointer tree-item" ng-if="showLabel" ng-class="getClass(level - 1)" droppable="node">
<div class="dropdown pull-right"> <div class="dropdown pull-right">
<div class="pull-right" ng-click="showCategoryDetails(node)"> <div class="pull-right" ng-click="showCategoryDetails(node.id, node.isTag)">
<i class="icon-wrench config pointer"></i> <i class="icon-wrench config pointer"></i>
</div> </div>
</div> </div>
<div ng-click="categoryClicked(node.id)" ng-dblclick="showCategoryDetails(node)"> <div ng-click="categoryClicked(node.id, node.isTag)" ng-dblclick="showCategoryDetails(node.id, node.isTag)">
<span class="fldr"> <span class="fldr">
<i ng-class="{'icon-caret-right': !node.expanded, 'icon-caret-down': node.expanded}" ng-click="toggleCategory(node, $event)" ng-show="showChildren"></i> <i ng-class="{'icon-caret-right': !node.expanded, 'icon-caret-down': node.expanded}" ng-click="toggleCategory(node, $event)" ng-show="showChildren"></i>
<i ng-class="{'icon-star' : node.id == 'starred', 'icon-inbox': node.id == 'all'}" ng-show="!showChildren"></i> <i ng-class="{'icon-star' : node.id == 'starred', 'icon-inbox': node.id == 'all', 'icon-tag' : node.isTag}" ng-show="!showChildren"></i>
</span> </span>
<span ng-class="{selected: (node.id == selectedId && selectedType == 'category')}"> <span ng-class="{selected: (node.id == selectedId && selectedType == 'category')}">
<span ng-class="{unread: unreadCount({category:node})}" class="bidi-embed"> <span ng-class="{unread: unreadCount({category:node})}" class="bidi-embed">

View File

@@ -0,0 +1,12 @@
<span>
<a ng-click="edit_mode=!edit_mode" class="nolink pointer">
<i class="icon-tags"></i>
${global.tags}
</a>
<span ng-if="!edit_mode">
<span class="label label-info" ng-repeat="tag in entry.tags">{{tag}}</span>
</span>
<span ng-if="edit_mode">
<input type="text" ui-select2="select2Options" ng-model="entry.tags" class="tag-input" />
</span>
</span>

View File

@@ -1,13 +1,15 @@
<div class="css-treeview" ng-controller="CategoryTreeCtrl"> <div class="css-treeview" ng-controller="CategoryTreeCtrl">
<ul> <ul>
<category node="CategoryService.subscriptions" show-label="'${tree.all}'" show-children="false" <category node="CategoryService.subscriptions" show-label="'${tree.all}'" show-children="false" level="0" selected-type="selectedType"
level="0" selected-type="selectedType" selected-id="selectedId" selected-id="selectedId" unread-count="unreadCount(category)"> </category>
unread-count="unreadCount(category)"> </category> <category node="starred" show-label="'${tree.starred}'" show-children="false" level="0" selected-type="selectedType"
<category node="starred" show-label="'${tree.starred}'" show-children="false" selected-id="selectedId" unread-count="unreadCount(category)"> </category>
level="0" selected-type="selectedType" selected-id="selectedId" <category node="CategoryService.subscriptions" show-label="false" show-children="true" level="0" selected-type="selectedType"
unread-count="unreadCount(category)"> </category> selected-id="selectedId" unread-count="unreadCount(category)"> </category>
<category node="CategoryService.subscriptions" show-label="false" show-children="true"
level="0" selected-type="selectedType" selected-id="selectedId" <li ng-repeat="tag in tags | orderBy: 'name'">
unread-count="unreadCount(category)"> </category> <category node="tag" show-label="tag.name" show-children="false" level="0" selected-type="selectedType" selected-id="selectedId"
unread-count="unreadCount(category)"> </category>
</li>
</ul> </ul>
</div> </div>

View File

@@ -0,0 +1,19 @@
<div>
<div class="page-header">
<h3>${details.tag_details}</h3>
</div>
<form name="form" class="form-horizontal" ng-submit="save()">
<div class="control-group">
<label class="control-label">${details.feed_url}</label>
<div class="controls horizontal-align">
<a ng-show="user.apiKey" href="{{'rest/category/entriesAsFeed?id=all&tag=' + tag + '&apiKey=' + user.apiKey}}" target="_blank">${global.link}</a>
<span ng-show="!user.apiKey">${details.generate_api_key_first}</span>
</div>
</div>
<div class="form-actions">
<button type="button" class="btn" ng-click="back()">${global.cancel}</button>
</div>
</form>
</div>

View File

@@ -13,9 +13,9 @@
<span ng-show="keywords">${view.search_for} '{{keywords}}'</span> <span ng-show="keywords">${view.search_for} '{{keywords}}'</span>
</h3> </h3>
</div> </div>
<div infinite-scroll="loadMoreEntries()" infinite-scroll-disabled="busy || !settingsService.settings.readingMode" infinite-scroll-distance="1" id="feed-accordion" <div infinite-scroll="loadMoreEntries()" infinite-scroll-disabled="busy || !settingsService.settings.readingMode"
ng-class="{'expanded' : settingsService.settings.viewMode == 'expanded' }"> infinite-scroll-distance="1" id="feed-accordion" ng-class="{'expanded' : settingsService.settings.viewMode == 'expanded' }">
<div ng-show="message && errorCount > 10">${view.error_while_loading_feed} : {{message}}</div> <div ng-show="message && errorCount > 10">${view.error_while_loading_feed} : {{message}}</div>
<div ng-repeat="entry in entries" class="entry entry-font-size-{{font_size}}" id="entry_{{entry.id}}" <div ng-repeat="entry in entries" class="entry entry-font-size-{{font_size}}" id="entry_{{entry.id}}"
ng-class="{unread: entry.read == false, current: current==entry, open: isOpen, closed: !isOpen }"> ng-class="{unread: entry.read == false, current: current==entry, open: isOpen, closed: !isOpen }">
@@ -23,8 +23,7 @@
<a href="{{entry.url}}" target="_blank" class="entry-heading-link" ng-click="noop($event)" ng-mouseup="entryClicked(entry, $event)"> <a href="{{entry.url}}" target="_blank" class="entry-heading-link" ng-click="noop($event)" ng-mouseup="entryClicked(entry, $event)">
<span class="feed-name"> <span class="feed-name">
<span class="star" ng-mouseup="star(entry, !entry.starred, $event)"> <span class="star" ng-mouseup="star(entry, !entry.starred, $event)">
<i ng-class="{'icon-star icon-star-yellow': entry.starred, 'icon-star-empty': !entry.starred}" <i ng-class="{'icon-star icon-star-yellow': entry.starred, 'icon-star-empty': !entry.starred}" class="pointer"></i>
class="pointer"></i>
</span> </span>
<favicon url="entry.iconUrl" /> <favicon url="entry.iconUrl" />
{{entry.feedName}} {{entry.feedName}}
@@ -36,15 +35,17 @@
<i class="icon-external-link"></i> <i class="icon-external-link"></i>
</a> </a>
</div> </div>
<div class="entry-body" ng-if="settingsService.settings.viewMode == 'expanded' || (isOpen && current == entry)" ng-mouseup="bodyClicked(entry, $event)" <div class="entry-body" ng-if="settingsService.settings.viewMode == 'expanded' || (isOpen && current == entry)"
ng-class="{rtl: entry.rtl}"> ng-mouseup="bodyClicked(entry, $event)" ng-class="{rtl: entry.rtl}">
<div class="entry-header"> <div class="entry-header">
<div class="entry-title"> <div class="entry-title">
<a href="{{entry.url}}" target="_blank" ng-bind-html-unsafe="entry.title | highlight:keywords"></a> <a href="{{entry.url}}" target="_blank" ng-bind-html-unsafe="entry.title | highlight:keywords"></a>
<div class="entry-subtitle"> <div class="entry-subtitle">
<span class="entry-source" ng-if="selectedType == 'category'"> <span class="entry-source" ng-if="selectedType == 'category'">
<span class="entry-source-prefix">${view.entry_source}</span> <span class="entry-source-prefix">${view.entry_source}</span>
<a ng-click="goToFeed(entry.feedId)" class="pointer bidi-embed"><span>{{entry.feedName}}</span></a> <a ng-click="goToFeed(entry.feedId)" class="pointer bidi-embed">
<span>{{entry.feedName}}</span>
</a>
</span> </span>
<span class="entry-author" ng-if="entry.author"> <span class="entry-author" ng-if="entry.author">
<span class="entry-author-prefix">${view.entry_author}</span> <span class="entry-author-prefix">${view.entry_author}</span>
@@ -66,16 +67,13 @@
<div ng-if="entry.enclosureType && entry.enclosureType.indexOf('image') == 0"> <div ng-if="entry.enclosureType && entry.enclosureType.indexOf('image') == 0">
<img ng-src="{{entry.enclosureUrl}}" /> <img ng-src="{{entry.enclosureUrl}}" />
</div> </div>
<a href="{{entry.enclosureUrl}}" target="_blank" ng-if="entry.enclosureType" download> <a href="{{entry.enclosureUrl}}" target="_blank" ng-if="entry.enclosureType" download> ${global.download} </a>
${global.download}
</a>
</div> </div>
</div> </div>
<div class="entry-buttons form-horizontal"> <div class="entry-buttons form-horizontal">
<span class="star" ng-mouseup="star(entry, !entry.starred, $event)"> <span class="star" ng-mouseup="star(entry, !entry.starred, $event)">
<i ng-class="{'icon-star icon-star-yellow': entry.starred, 'icon-star-empty': !entry.starred}" <i ng-class="{'icon-star icon-star-yellow': entry.starred, 'icon-star-empty': !entry.starred}" class="pointer"></i>
class="pointer"></i>
</span> </span>
<label class="checkbox inline" ng-if="entry.markable"> <label class="checkbox inline" ng-if="entry.markable">
<input type="checkbox" ng-checked="!entry.read" ng-click="mark(entry, !entry.read)" class="mousetrap"></input> <input type="checkbox" ng-checked="!entry.read" ng-click="mark(entry, !entry.read)" class="mousetrap"></input>
@@ -86,7 +84,8 @@
<a href="mailto:?subject={{entry.title|escape}}&body={{entry.url|escape}}" title="E-mail" popup> <a href="mailto:?subject={{entry.title|escape}}&body={{entry.url|escape}}" title="E-mail" popup>
<i class="icon-envelope"></i> <i class="icon-envelope"></i>
</a> </a>
<a href="https://mail.google.com/mail/?view=cm&fs=1&tf=1&source=mailto&su={{entry.title|escape}}&body={{entry.url|escape}}" title="Gmail" popup> <a href="https://mail.google.com/mail/?view=cm&fs=1&tf=1&source=mailto&su={{entry.title|escape}}&body={{entry.url|escape}}"
title="Gmail" popup>
<i class="icon-gmail"></i> <i class="icon-gmail"></i>
</a> </a>
<a href="http://www.facebook.com/sharer.php?u=={{entry.url|escape}}" title="Facebook" popup> <a href="http://www.facebook.com/sharer.php?u=={{entry.url|escape}}" title="Facebook" popup>
@@ -108,7 +107,10 @@
<i class="icon-buffer"></i> <i class="icon-buffer"></i>
</a> </a>
</span> </span>
<span class="tags-panel">
<tags entry="entry"></tags>
</span>
<a ng-click="markUpTo(entry)" class="mark-up-to" title="${view.mark_up_to_here}"> <a ng-click="markUpTo(entry)" class="mark-up-to" title="${view.mark_up_to_here}">
<i class="icon-step-forward icon-rotate-90"></i> <i class="icon-step-forward icon-rotate-90"></i>
</a> </a>