Compare commits

..

129 Commits
1.3.0 ... 1.4.0

Author SHA1 Message Date
Athou
cdcbfbff68 stable enough, time to tag 2014-02-24 16:03:38 +01:00
Athou
6860940afc fix javax.net.ssl.SSLProtocolException: handshake alert:
unrecognized_name (fix #549)
2014-02-22 13:12:55 +01:00
Athou
bfc2ee3663 readme update 2014-02-20 11:22:42 +01:00
Athou
b104622081 switch to bonecp 2014-02-20 10:32:16 +01:00
Athou
a861387bd7 handle null categories 2014-02-14 15:41:17 +01:00
Athou
b0f2260fad dependencies update 2014-02-12 21:02:00 +01:00
Athou
97f0d98ffd Merge pull request #544 from ebraminio/master
Update Persian translation
2014-02-03 04:23:11 -08:00
Ebrahim Byagowi
1ad58a029c Update Persian translation 2014-02-03 15:49:09 +03:30
Athou
4c27da0433 propagate exception 2014-02-02 12:30:41 +01:00
Athou
faf69b43c3 fix aspect ratio for large images 2014-02-02 12:19:52 +01:00
Athou
7fff561268 switch to dbcp as tomcat-pool seems to leak connections 2014-01-09 09:13:30 +01:00
Athou
5e1360a65b smarter log cleanup script (#533) 2014-01-07 12:02:15 +01:00
Athou
cc92d2f546 Merge pull request #537 from Busimus/patch-3
Fixed typo.
2014-01-07 02:53:14 -08:00
Athou
def75a250f Merge pull request #538 from Busimus/patch-6
Updated ru.properties
2014-01-07 02:52:53 -08:00
Alexander Bus
15cd7caf9b Update ru.properties 2014-01-06 23:05:41 +07:00
Alexander Bus
41a51530ef Fixed typo. 2014-01-06 22:14:13 +07:00
Athou
3a101941b3 mark as read when swiping entry title to the right 2013-12-18 18:36:22 +01:00
Athou
0976fee4df fix left padding on mobile 2013-12-13 17:42:40 +01:00
Athou
f87da777da improved support for fxos 2013-12-13 17:29:48 +01:00
Athou
e1c2bf0890 Merge pull request #534 from JKakku/patch-3
Translated confirmation messages
2013-12-12 22:46:25 -08:00
JKakku
b829defb30 Translated confirmation messages 2013-12-13 01:12:41 +02:00
Athou
fa8770d2a7 restore main content padding 2013-12-12 15:12:43 +01:00
Athou
222c8a65af git plugin update 2013-12-12 13:11:07 +01:00
Athou
76f5b67ac4 openshift changes (fix #532) 2013-12-12 11:51:48 +01:00
Athou
1791d49efe resizeable subscription list 2013-12-12 11:51:48 +01:00
Athou
64e1b5df09 fix sync during development 2013-12-12 10:25:08 +01:00
Athou
e1ff077623 Merge pull request #531 from LpSamuelm/patch-20
Translated new labels to Swedish
2013-12-11 01:24:42 -08:00
LpSamuelm
1361072558 Translated new labels to Swedish
Translatin' labels! Oh yeah!
2013-12-11 10:23:20 +01:00
Athou
5119434d21 more metrics 2013-12-10 14:02:06 +01:00
Athou
b29540b14e first add feeds from the queue, then if needed fetch feeds from the database to fill the batch 2013-12-10 11:08:52 +01:00
Athou
e69785bb89 smaller margins on mobile 2013-12-10 09:28:58 +01:00
Athou
76465fee07 remove horizontal scrolling on mobile 2013-12-06 04:12:19 +01:00
Athou
b52c459ebb calculate offset correctly for tags and starred listing (fix #530) 2013-12-05 12:25:17 +01:00
Athou
1d73982545 apply where clause when predicate list has been populated 2013-12-05 12:23:59 +01:00
Athou
74f6c45f36 Merge pull request #527 from evenorbert/patch-2
Update hu.properties
2013-12-02 07:20:56 -08:00
Norbert Evenich
0490b528e4 Update hu.properties
Updated hungarian translation.
2013-12-02 16:07:26 +01:00
Athou
ffa1e14449 feed url autofocus 2013-11-29 16:13:26 +01:00
Athou
b8fe89b2f4 tomee upgrade 2013-11-29 16:10:43 +01:00
Athou
94b293202c support for meego devices 2013-11-29 12:19:55 +01:00
Athou
7ef143a642 mousetrap upgrade 2013-11-29 11:24:35 +01:00
Athou
057f6916e9 spinjs upgrade 2013-11-29 11:24:29 +01:00
Athou
e24e892cb3 jquery upgrade 2013-11-29 11:22:03 +01:00
Athou
78976b06e2 lodash upgrade 2013-11-29 11:21:42 +01:00
Athou
96cfcd5b2b angularjs upgrade 2013-11-29 10:15:29 +01:00
Athou
12bda0122c make images fit available width 2013-11-28 19:35:31 +01:00
Athou
4ac4e5abf2 commons collection upgrade to 4.0 2013-11-28 16:56:52 +01:00
Athou
268f0f53a8 close tree when button clicked on mobile 2013-11-28 11:58:53 +01:00
Athou
71521f3428 fix mobile layout 2013-11-28 10:45:10 +01:00
Athou
6101fb2bef new translations 2013-11-28 10:12:52 +01:00
Athou
8f6aa0896b fix bootstrap dialogs 2013-11-28 10:12:10 +01:00
Athou
b8f0af5b2e fix radio buttons 2013-11-28 09:15:14 +01:00
Athou
32730f6c41 force full refreshes only when under heavy load 2013-11-27 15:54:23 +01:00
Athou
7caa99f8f2 bootstrap3 2013-11-27 15:54:23 +01:00
Athou
4f8e2ab478 limit transaction size 2013-11-27 08:07:44 +01:00
Athou
5c44f392ca remove warning 2013-11-26 15:11:44 +01:00
Athou
174d21fd4e move logic to user service 2013-11-26 15:09:32 +01:00
Athou
c2ed6d47f1 force a full refresh of the user's feeds when he logs in 2013-11-26 07:05:59 +01:00
Athou
0f6f717d09 tweaking batch size again 2013-11-20 11:33:21 +01:00
Athou
d7fb637f68 same batch size for all operations 2013-11-16 11:27:48 +01:00
Athou
fce9086b27 remove deprecated duplicate feed detection 2013-11-16 07:40:44 +01:00
Athou
97586cd2c8 batch delete entries too 2013-11-16 07:30:01 +01:00
Athou
b74458f0b0 more logging 2013-11-15 21:38:11 +01:00
Athou
7c7a0fceaf reduce batch size for feeds 2013-11-15 15:53:22 +01:00
Athou
425a8880cd smaller preview image 2013-11-14 15:27:31 +01:00
Athou
23fe90ec64 fix log message 2013-11-14 14:41:41 +01:00
Athou
c01ec5d039 fix openshift log cleanup script 2013-11-14 13:02:24 +01:00
Athou
4f284165c2 more database cleanup tasks 2013-11-14 12:56:08 +01:00
Athou
2a62ccff11 jackson upgrade 2013-11-14 12:42:07 +01:00
Athou
d09cf472dd disable services not needed 2013-11-13 16:26:23 +01:00
Athou
5c721ae6f5 prevent scanning classes twice 2013-11-13 16:18:34 +01:00
Athou
2bb8fcdb5f scan our classes only 2013-11-13 16:17:10 +01:00
Athou
6eda93098b trust should be the last filter 2013-11-12 15:52:30 +01:00
Athou
6344f554d6 rewrite iframes to use https if commafeed uses https 2013-11-12 15:44:56 +01:00
Athou
7e4c1f374c use latest wro4j in prod profile, using latest jruby version (fix #315) 2013-11-12 11:49:11 +01:00
Athou
28eaab7f7d trust enclosure urls 2013-11-12 11:35:22 +01:00
Athou
1937944f7e fix search 2013-11-12 09:57:59 +01:00
Athou
3b4b84fdab formatting 2013-11-12 09:45:26 +01:00
Athou
32325bb49c angularjs 1.2.0 upgrade 2013-11-12 09:43:42 +01:00
Athou
c01c1e93f9 lombok upgrade 2013-11-12 08:53:38 +01:00
Athou
eac096019f jsoup upgrade 2013-11-12 08:53:22 +01:00
Athou
9f9389e846 liquibase upgrade 2013-11-12 08:53:05 +01:00
Athou
a71317881f wicket upgrade 2013-11-12 08:52:46 +01:00
Athou
7092824c96 grid dates formatting (fix #421) 2013-11-08 11:37:45 +01:00
Athou
0ff998bbd7 bigger items on mobile 2013-11-08 11:37:45 +01:00
Athou
fc318ad211 smarter mobile detection (fix #255 and fix #487) 2013-11-08 11:37:44 +01:00
Athou
73323335cb cosmetic fix 2013-11-08 11:37:44 +01:00
Athou
ef57c5523d Merge pull request #523 from utimukat55/translate_ja
Add translation ja
2013-11-07 06:10:46 -08:00
utimukat55
846f4a7222 Add translation ja 2013-11-07 21:27:14 +09:00
Athou
05036778d6 httpclient upgrade 2013-11-05 15:27:19 +01:00
Athou
52df661238 mysql jdbc driver update 2013-10-29 13:26:39 +01:00
Athou
7957dc237e for generated feeds, set 'all' as default instead of 'unread' 2013-10-29 07:05:19 +01:00
Athou
3fe419ba2f disable openejb stats 2013-10-24 14:50:51 +02:00
Athou
61944656b8 allow same query parameters for entriesAsFeed (fix #521) 2013-10-24 09:50:35 +02:00
Athou
1cb997b66d moved query 2013-10-24 09:50:35 +02:00
Athou
89463808db return exception stacktrace in the body 2013-10-23 07:55:14 +02:00
Athou
6aca66d8cf prevent NPE 2013-10-20 17:12:53 +02:00
Athou
38f8102fb3 readability support (fix #108) 2013-10-20 15:26:58 +02:00
Athou
e709499240 Merge pull request #520 from LpSamuelm/patch-19
Translated new labels to Swedish
2013-10-15 18:34:45 -07:00
LpSamuelm
0b714d5e52 Translated new labels to Swedish
WOAH TAGS
2013-10-16 03:32:12 +02:00
Athou
98e4f0c6dc Merge pull request #519 from ekovi/patch-1
updated
2013-10-15 11:18:32 -07:00
ekovi
d82d0af565 updated 2013-10-15 19:21:00 +02:00
Athou
d8abb7039d Merge pull request #518 from JKakku/patch-2
Update fi.properties
2013-10-15 09:22:26 -07:00
JKakku
84dc11048d Update fi.properties
Added the missing stuff since the last update couple of months ago.
2013-10-15 18:17:31 +03:00
Athou
bad915bbaa fix warnings 2013-10-14 15:57:35 +02:00
Athou
287dea2d36 use tag for feed name is available 2013-10-14 07:41:19 +02:00
Athou
a0b937769d autofocus the input when it appears 2013-10-14 07:01:14 +02:00
Athou
6acef4a406 fix mark up to link positioning on mobile 2013-10-14 02:44:48 +02:00
Athou
8b77eb9850 add valid checksum (fix #517) 2013-10-14 02:15:03 +02:00
Athou
6f22836dcb remove double slash in image 2013-10-13 21:34:53 +02:00
Athou
a4347c8878 highlight tag if selected 2013-10-13 16:02:59 +02:00
Athou
836f7eff09 better index usage 2013-10-13 12:15:41 +02:00
Athou
c993bd472d cleanup 2013-10-13 12:04:29 +02:00
Athou
431ab92a02 tagging support (#96) 2013-10-13 11:58:22 +02:00
Athou
94f469a6b1 js dependencies update 2013-10-13 10:49:03 +02:00
Athou
3fec1c6890 dependencies update 2013-10-12 17:41:07 +02:00
Athou
f8316911bd another typo 2013-10-12 14:57:01 +02:00
Athou
642d1f6be5 typo 2013-10-12 14:56:24 +02:00
Athou
5a82c3a130 Merge pull request #515 from rthome/master
Translate new strings to German
2013-10-12 00:10:15 -07:00
Raffael Thome
6a8174afac Translate new strings to German 2013-10-12 08:17:49 +02:00
Athou
f4c86634f7 liquibase upgrade, removing dirty fix 2013-10-10 10:07:16 +02:00
Athou
322e588a4e Merge pull request #514 from LpSamuelm/patch-18
Translated new labels to Swedish
2013-10-07 22:06:03 -07:00
Athou
822dee7a13 scale better, don't block when the pool is exhausted 2013-10-08 07:05:31 +02:00
LpSamuelm
101e179788 Translated new labels to Swedish 2013-10-07 21:04:35 +02:00
Athou
57abee6cf0 use the url of the feed as the base url to resolve relative entry links when the declared link in the feed is relative 2013-10-03 12:42:05 +02:00
Athou
b615847b09 make sure entries with same update date are always sorted the same way 2013-10-03 11:05:13 +02:00
Athou
ffef87e249 customizable scrolling speed 2013-10-03 10:40:58 +02:00
Athou
ba3b8df4c9 mark older than half a day 2013-10-03 10:02:16 +02:00
Athou
40175d3e54 dependencies update 2013-09-25 13:51:09 +02:00
Athou
06b047cfe6 1.4.0 snapshot 2013-09-25 13:48:29 +02:00
181 changed files with 3890 additions and 3160 deletions

View File

@@ -261,7 +261,7 @@
<property name="external_port">${env.OPENSHIFT_JBOSSEAP_CLUSTER_PROXY_PORT} <property name="external_port">${env.OPENSHIFT_JBOSSEAP_CLUSTER_PROXY_PORT}
</property> </property>
<property name="bind_port">7600</property> <property name="bind_port">7600</property>
<property name="bind_addr">${env.OPENSHIFT_INTERNAL_IP}</property> <property name="bind_addr">${env.OPENSHIFT_JBOSSEAP_IP}</property>
</transport> </transport>
<protocol type="TCPPING"> <protocol type="TCPPING">
<property name="timeout">3000</property> <property name="timeout">3000</property>
@@ -476,15 +476,15 @@
<interfaces> <interfaces>
<interface name="management"> <interface name="management">
<loopback-address value="${env.OPENSHIFT_INTERNAL_IP}" /> <loopback-address value="${env.OPENSHIFT_JBOSSEAP_IP}" />
</interface> </interface>
<interface name="public"> <interface name="public">
<loopback-address value="${env.OPENSHIFT_INTERNAL_IP}" /> <loopback-address value="${env.OPENSHIFT_JBOSSEAP_IP}" />
</interface> </interface>
<interface name="unsecure"> <interface name="unsecure">
<!-- Used for IIOP sockets in the standarad configuration. To secure JacORB <!-- Used for IIOP sockets in the standarad configuration. To secure JacORB
you need to setup SSL --> you need to setup SSL -->
<loopback-address value="${env.OPENSHIFT_INTERNAL_IP}" /> <loopback-address value="${env.OPENSHIFT_JBOSSEAP_IP}" />
</interface> </interface>
</interfaces> </interfaces>

View File

@@ -1 +1,7 @@
rm -rf $OPENSHIFT_JBOSSAS_LOG_DIR\*.log.* if [ $OPENSHIFT_JBOSSAS_LOG_DIR ]; then
rm -rf $OPENSHIFT_JBOSSAS_LOG_DIR/*.log.*
fi
if [ $OPENSHIFT_JBOSSEAP_LOG_DIR ]; then
rm -rf $OPENSHIFT_JBOSSEAP_LOG_DIR/*.log.*
fi

View File

@@ -41,7 +41,7 @@ To install maven and openjdk on Ubuntu, issue the following commands
sudo apt-get update sudo apt-get update
sudo apt-get install openjdk-7-jdk maven3 sudo apt-get install openjdk-7-jdk maven3
Not required but if you don't, use 'mvn3' instead of 'mvn' for the rest of the instructions. # Not required but if you don't, use 'mvn3' instead of 'mvn' for the rest of the instructions.
sudo ln -s /usr/bin/mvn3 /usr/bin/mvn sudo ln -s /usr/bin/mvn3 /usr/bin/mvn
On Windows and other operating systems, just download maven 3.x from the [official site](http://maven.apache.org/), extract it somewhere and add the `bin` directory to your `PATH` environment variable. On Windows and other operating systems, just download maven 3.x from the [official site](http://maven.apache.org/), extract it somewhere and add the `bin` directory to your `PATH` environment variable.
@@ -54,16 +54,16 @@ If you don't have git you can download the sources as a zip file from [here](htt
Now build the application Now build the application
Embedded HSQL database: # Embedded HSQL database:
mvn clean package tomee:build -Pprod mvn clean package tomee:build -Pprod
External MySQL database: # External MySQL database:
mvn clean package tomee:build -Pprod -Pmysql mvn clean package tomee:build -Pprod -Pmysql
External PostgreSQL database: # External PostgreSQL database:
mvn clean package tomee:build -Pprod -Ppgsql mvn clean package tomee:build -Pprod -Ppgsql
External Microsoft SQL Server database: # External Microsoft SQL Server database:
mvn clean package tomee:build -Pprod -Pmssql mvn clean package tomee:build -Pprod -Pmssql
It will generate a zip file at `target/commafeed.zip` with everything you need to run the application. It will generate a zip file at `target/commafeed.zip` with everything you need to run the application.
@@ -81,7 +81,7 @@ This will generate the file `target/commafeed.war`. Copy this file to your tomee
* The application is online at [http://localhost:8082/commafeed](http://localhost:8082/commafeed). Don't forget to set the public URL in the admin settings. * The application is online at [http://localhost:8082/commafeed](http://localhost:8082/commafeed). Don't forget to set the public URL in the admin settings.
* The default user is `admin` and the password is `admin`. * The default user is `admin` and the password is `admin`.
You can use nginix or apache as a proxy http server. Note that when using apache, the `ProxyPreserveHost on` option should be `set in your config file. You can use nginx or apache as a proxy http server. Note that when using apache, the `ProxyPreserveHost on` option should be set in your config file.
Local development Local development
----------------- -----------------

View File

@@ -1 +1 @@
set JAVA_OPTS=-Djava.net.preferIPv4Stack=true -Xmx1024m -XX:MaxPermSize=256m -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC set JAVA_OPTS=-Djava.net.preferIPv4Stack=true -Xmx1024m -XX:MaxPermSize=256m -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC -Djsse.enableSNIExtension=false

View File

@@ -1 +1 @@
export JAVA_OPTS="-Djava.net.preferIPv4Stack=true -Xmx1024m -XX:MaxPermSize=256m -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC" export JAVA_OPTS="-Djava.net.preferIPv4Stack=true -Xmx1024m -XX:MaxPermSize=256m -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC -Djsse.enableSNIExtension=false"

49
pom.xml
View File

@@ -4,7 +4,7 @@
<groupId>com.commafeed</groupId> <groupId>com.commafeed</groupId>
<artifactId>commafeed</artifactId> <artifactId>commafeed</artifactId>
<version>1.3.0</version> <version>1.4.0</version>
<packaging>war</packaging> <packaging>war</packaging>
<name>CommaFeed</name> <name>CommaFeed</name>
@@ -79,7 +79,7 @@
<artifactId>tomee-maven-plugin</artifactId> <artifactId>tomee-maven-plugin</artifactId>
<version>1.5.2</version> <version>1.5.2</version>
<configuration> <configuration>
<tomeeVersion>1.5.2</tomeeVersion> <tomeeVersion>1.6.0</tomeeVersion>
<tomeeClassifier>plus</tomeeClassifier> <tomeeClassifier>plus</tomeeClassifier>
<tomeeHttpPort>8082</tomeeHttpPort> <tomeeHttpPort>8082</tomeeHttpPort>
<args>-Xmx1024m -XX:MaxPermSize=512m -XX:+CMSClassUnloadingEnabled</args> <args>-Xmx1024m -XX:MaxPermSize=512m -XX:+CMSClassUnloadingEnabled</args>
@@ -104,13 +104,18 @@
<lib>org.hibernate.common:hibernate-commons-annotations:4.0.1.Final</lib> <lib>org.hibernate.common:hibernate-commons-annotations:4.0.1.Final</lib>
<lib>org.hibernate:hibernate-validator:4.3.1.Final</lib> <lib>org.hibernate:hibernate-validator:4.3.1.Final</lib>
<lib>org.jboss.logging:jboss-logging:3.1.3.GA</lib> <lib>org.jboss.logging:jboss-logging:3.1.3.GA</lib>
<lib>org.javassist:javassist:3.15.0-GA</lib>
<lib>org.apache.openejb:openejb-bonecp:4.6.0</lib>
<lib>com.jolbox:bonecp:0.8.0.RELEASE</lib>
<lib>com.google.guava:guava:14.0.1</lib>
<lib>dom4j:dom4j:1.6.1</lib> <lib>dom4j:dom4j:1.6.1</lib>
<lib>antlr:antlr:2.7.7</lib> <lib>antlr:antlr:2.7.7</lib>
<lib>remove:openjpa-</lib> <lib>remove:openjpa-</lib>
<lib>remove:hsqldb</lib> <lib>remove:hsqldb</lib>
<lib>org.hsqldb:hsqldb:2.3.0</lib> <lib>org.hsqldb:hsqldb:2.3.0</lib>
<lib>mysql:mysql-connector-java:5.1.24</lib> <lib>mysql:mysql-connector-java:5.1.26</lib>
<lib>postgresql:postgresql:9.1-901.jdbc4</lib> <lib>postgresql:postgresql:9.1-901.jdbc4</lib>
<lib>net.sourceforge.jtds:jtds:1.3.1</lib> <lib>net.sourceforge.jtds:jtds:1.3.1</lib>
@@ -169,7 +174,7 @@
<plugin> <plugin>
<groupId>pl.project13.maven</groupId> <groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId> <artifactId>git-commit-id-plugin</artifactId>
<version>2.1.5</version> <version>2.1.7</version>
<executions> <executions>
<execution> <execution>
<goals> <goals>
@@ -189,7 +194,7 @@
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<version>0.12.0</version> <version>1.12.4</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
@@ -214,28 +219,28 @@
<dependency> <dependency>
<groupId>redis.clients</groupId> <groupId>redis.clients</groupId>
<artifactId>jedis</artifactId> <artifactId>jedis</artifactId>
<version>2.1.0</version> <version>2.2.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.liquibase</groupId> <groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId> <artifactId>liquibase-core</artifactId>
<version>3.0.2</version> <version>3.0.7</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.google.guava</groupId> <groupId>com.google.guava</groupId>
<artifactId>guava</artifactId> <artifactId>guava</artifactId>
<version>14.0.1</version> <version>16.0.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>commons-beanutils</groupId> <groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId> <artifactId>commons-beanutils</artifactId>
<version>1.8.3</version> <version>1.9.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>commons-codec</groupId> <groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId> <artifactId>commons-codec</artifactId>
<version>1.8</version> <version>1.9</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>commons-collections</groupId> <groupId>commons-collections</groupId>
@@ -260,7 +265,7 @@
<dependency> <dependency>
<groupId>commons-fileupload</groupId> <groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId> <artifactId>commons-fileupload</artifactId>
<version>1.3</version> <version>1.3.1</version>
</dependency> </dependency>
<dependency> <dependency>
@@ -298,34 +303,34 @@
<dependency> <dependency>
<groupId>com.google.gwt</groupId> <groupId>com.google.gwt</groupId>
<artifactId>gwt-servlet</artifactId> <artifactId>gwt-servlet</artifactId>
<version>2.5.1</version> <version>2.6.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>net.sourceforge.cssparser</groupId> <groupId>net.sourceforge.cssparser</groupId>
<artifactId>cssparser</artifactId> <artifactId>cssparser</artifactId>
<version>0.9.9</version> <version>0.9.13</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.httpcomponents</groupId> <groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId> <artifactId>httpclient</artifactId>
<version>4.2.5</version> <version>4.3.2</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jsoup</groupId> <groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId> <artifactId>jsoup</artifactId>
<version>1.7.2</version> <version>1.7.3</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId> <artifactId>jackson-databind</artifactId>
<version>2.2.2</version> <version>2.3.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId> <artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version> <version>1.7.6</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>log4j</groupId> <groupId>log4j</groupId>
@@ -336,23 +341,23 @@
<dependency> <dependency>
<groupId>org.apache.wicket</groupId> <groupId>org.apache.wicket</groupId>
<artifactId>wicket-core</artifactId> <artifactId>wicket-core</artifactId>
<version>6.10.0</version> <version>6.13.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.wicket</groupId> <groupId>org.apache.wicket</groupId>
<artifactId>wicket-auth-roles</artifactId> <artifactId>wicket-auth-roles</artifactId>
<version>6.10.0</version> <version>6.13.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.wicket</groupId> <groupId>org.apache.wicket</groupId>
<artifactId>wicket-extensions</artifactId> <artifactId>wicket-extensions</artifactId>
<version>6.10.0</version> <version>6.13.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.wicket</groupId> <groupId>org.apache.wicket</groupId>
<artifactId>wicket-cdi</artifactId> <artifactId>wicket-cdi</artifactId>
<version>6.10.0</version> <version>6.13.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ro.isdc.wro4j</groupId> <groupId>ro.isdc.wro4j</groupId>
@@ -549,7 +554,7 @@
<plugin> <plugin>
<groupId>ro.isdc.wro4j</groupId> <groupId>ro.isdc.wro4j</groupId>
<artifactId>wro4j-maven-plugin</artifactId> <artifactId>wro4j-maven-plugin</artifactId>
<version>1.6.3</version> <version>1.7.2</version>
<executions> <executions>
<execution> <execution>
<goals> <goals>

View File

@@ -7,44 +7,33 @@ import java.security.cert.X509Certificate;
import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager; import javax.net.ssl.X509TrustManager;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.http.Consts;
import org.apache.http.Header; import org.apache.http.Header;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders; import org.apache.http.HttpHeaders;
import org.apache.http.HttpHost; import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus; import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException; import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.HttpResponseException; import org.apache.http.client.HttpResponseException;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.params.CookiePolicy; import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.client.params.HttpClientParams; import org.apache.http.config.ConnectionConfig;
import org.apache.http.conn.ClientConnectionManager; import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.scheme.Scheme; import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.impl.client.HttpClients;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.impl.client.DecompressingHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.SystemDefaultHttpClient;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils; import org.apache.http.util.EntityUtils;
import org.apache.wicket.util.io.IOUtils;
/** /**
* Smart HTTP getter: handles gzip, ssl, last modified and etag headers * Smart HTTP getter: handles gzip, ssl, last modified and etag headers
@@ -57,8 +46,6 @@ public class HttpGetter {
private static final String ACCEPT_LANGUAGE = "en"; private static final String ACCEPT_LANGUAGE = "en";
private static final String PRAGMA_NO_CACHE = "No-cache"; private static final String PRAGMA_NO_CACHE = "No-cache";
private static final String CACHE_CONTROL_NO_CACHE = "no-cache"; private static final String CACHE_CONTROL_NO_CACHE = "no-cache";
private static final String UTF8 = "UTF-8";
private static final String HTTPS = "https";
private static SSLContext SSL_CONTEXT = null; private static SSLContext SSL_CONTEXT = null;
static { static {
@@ -70,8 +57,6 @@ public class HttpGetter {
} }
} }
private static final X509HostnameVerifier VERIFIER = new DefaultHostnameVerifier();
public HttpResult getBinary(String url, int timeout) throws ClientProtocolException, IOException, NotModifiedException { public HttpResult getBinary(String url, int timeout) throws ClientProtocolException, IOException, NotModifiedException {
return getBinary(url, null, null, timeout); return getBinary(url, null, null, timeout);
} }
@@ -95,10 +80,11 @@ public class HttpGetter {
HttpResult result = null; HttpResult result = null;
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
HttpClient client = newClient(timeout); CloseableHttpClient client = newClient(timeout);
CloseableHttpResponse response = null;
try { try {
HttpGet httpget = new HttpGet(url); HttpGet httpget = new HttpGet(url);
HttpContext context = new BasicHttpContext(); HttpClientContext context = HttpClientContext.create();
httpget.addHeader(HttpHeaders.ACCEPT_LANGUAGE, ACCEPT_LANGUAGE); httpget.addHeader(HttpHeaders.ACCEPT_LANGUAGE, ACCEPT_LANGUAGE);
httpget.addHeader(HttpHeaders.PRAGMA, PRAGMA_NO_CACHE); httpget.addHeader(HttpHeaders.PRAGMA, PRAGMA_NO_CACHE);
@@ -112,7 +98,6 @@ public class HttpGetter {
httpget.addHeader(HttpHeaders.IF_NONE_MATCH, eTag); httpget.addHeader(HttpHeaders.IF_NONE_MATCH, eTag);
} }
HttpResponse response = null;
try { try {
response = client.execute(httpget, context); response = client.execute(httpget, context);
int code = response.getStatusLine().getStatusCode(); int code = response.getStatusLine().getStatusCode();
@@ -150,14 +135,15 @@ public class HttpGetter {
contentType = entity.getContentType().getValue(); contentType = entity.getContentType().getValue();
} }
} }
HttpUriRequest req = (HttpUriRequest) context.getAttribute(ExecutionContext.HTTP_REQUEST); HttpUriRequest req = (HttpUriRequest) context.getRequest();
HttpHost host = (HttpHost) context.getAttribute(ExecutionContext.HTTP_TARGET_HOST); HttpHost host = context.getTargetHost();
String urlAfterRedirect = req.getURI().isAbsolute() ? req.getURI().toString() : host.toURI() + req.getURI(); String urlAfterRedirect = req.getURI().isAbsolute() ? req.getURI().toString() : host.toURI() + req.getURI();
long duration = System.currentTimeMillis() - start; long duration = System.currentTimeMillis() - start;
result = new HttpResult(content, contentType, lastModifiedHeaderValue, eTagHeaderValue, duration, urlAfterRedirect); result = new HttpResult(content, contentType, lastModifiedHeaderValue, eTagHeaderValue, duration, urlAfterRedirect);
} finally { } finally {
client.getConnectionManager().shutdown(); IOUtils.closeQuietly(response);
IOUtils.closeQuietly(client);
} }
return result; return result;
} }
@@ -205,21 +191,24 @@ public class HttpGetter {
} }
} }
public static HttpClient newClient(int timeout) { public static CloseableHttpClient newClient(int timeout) {
DefaultHttpClient client = new SystemDefaultHttpClient(); HttpClientBuilder builder = HttpClients.custom();
builder.useSystemProperties();
builder.disableAutomaticRetries();
SSLSocketFactory ssf = new SSLSocketFactory(SSL_CONTEXT, VERIFIER); builder.setSslcontext(SSL_CONTEXT);
ClientConnectionManager ccm = client.getConnectionManager(); builder.setHostnameVerifier(SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
SchemeRegistry sr = ccm.getSchemeRegistry();
sr.register(new Scheme(HTTPS, 443, ssf));
HttpParams params = client.getParams(); RequestConfig.Builder configBuilder = RequestConfig.custom();
HttpClientParams.setCookiePolicy(params, CookiePolicy.IGNORE_COOKIES); configBuilder.setCookieSpec(CookieSpecs.IGNORE_COOKIES);
HttpProtocolParams.setContentCharset(params, UTF8); configBuilder.setSocketTimeout(timeout);
HttpConnectionParams.setConnectionTimeout(params, timeout); configBuilder.setConnectTimeout(timeout);
HttpConnectionParams.setSoTimeout(params, timeout); configBuilder.setConnectionRequestTimeout(timeout);
client.setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler(0, false)); builder.setDefaultRequestConfig(configBuilder.build());
return new DecompressingHttpClient(client);
builder.setDefaultConnectionConfig(ConnectionConfig.custom().setCharset(Consts.UTF_8).build());
return builder.build();
} }
public static class NotModifiedException extends Exception { public static class NotModifiedException extends Exception {
@@ -245,24 +234,4 @@ public class HttpGetter {
return null; return null;
} }
} }
private static class DefaultHostnameVerifier implements X509HostnameVerifier {
@Override
public void verify(String string, SSLSocket ssls) throws IOException {
}
@Override
public void verify(String string, X509Certificate xc) throws SSLException {
}
@Override
public void verify(String string, String[] strings, String[] strings1) throws SSLException {
}
@Override
public boolean verify(String string, SSLSession ssls) {
return true;
}
};
} }

View File

@@ -26,13 +26,23 @@ public class ScheduledTasks {
DatabaseCleaningService cleaner; DatabaseCleaningService cleaner;
/** /**
* clean old read statuses, runs every day at midnight * clean old read statuses
*/ */
@Schedule(hour = "0", persistent = false) @Schedule(hour = "*", persistent = false)
private void cleanupOldStatuses() { private void cleanupOldStatuses() {
Date threshold = applicationSettingsService.getUnreadThreshold(); Date threshold = applicationSettingsService.getUnreadThreshold();
if (threshold != null) { if (threshold != null) {
cleaner.cleanStatusesOlderThan(threshold); cleaner.cleanStatusesOlderThan(threshold);
} }
} }
/**
* clean feeds without subscriptions, then clean contents without entries
*/
@Schedule(hour = "*", persistent = false)
private void cleanFeedsAndContents() {
cleaner.cleanEntriesForFeedsWithoutSubscriptions();
cleaner.cleanFeedsWithoutSubscriptions();
cleaner.cleanContentsWithoutEntries();
}
} }

View File

@@ -4,9 +4,12 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Alternative; import javax.enterprise.inject.Alternative;
import org.apache.commons.pool.impl.GenericObjectPool;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Jedis; import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPool;
@@ -30,7 +33,14 @@ public class RedisCacheService extends CacheService {
private static ObjectMapper mapper = new ObjectMapper(); private static ObjectMapper mapper = new ObjectMapper();
private JedisPool pool = new JedisPool(new JedisPoolConfig(), "localhost"); private JedisPool pool;
@PostConstruct
private void init() {
JedisPoolConfig config = new JedisPoolConfig();
config.setWhenExhaustedAction(GenericObjectPool.WHEN_EXHAUSTED_GROW);
pool = new JedisPool(config, "localhost");
}
@Override @Override
public List<String> getLastEntries(Feed feed) { public List<String> getLastEntries(Feed feed) {

View File

@@ -6,15 +6,12 @@ import java.util.List;
import javax.ejb.Stateless; import javax.ejb.Stateless;
import javax.persistence.TypedQuery; import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Join; import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType; import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root; import javax.persistence.criteria.Root;
import javax.persistence.criteria.SetJoin; import javax.persistence.criteria.SetJoin;
import javax.persistence.criteria.Subquery; import javax.persistence.criteria.Subquery;
import javax.persistence.metamodel.SingularAttribute;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
@@ -26,7 +23,6 @@ import com.commafeed.backend.model.FeedSubscription_;
import com.commafeed.backend.model.Feed_; import com.commafeed.backend.model.Feed_;
import com.commafeed.backend.model.User; import com.commafeed.backend.model.User;
import com.commafeed.backend.model.User_; import com.commafeed.backend.model.User_;
import com.commafeed.frontend.model.FeedCount;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
@@ -95,8 +91,8 @@ public class FeedDAO extends GenericDAO<Feed> {
public List<Feed> findByTopic(String topic) { public List<Feed> findByTopic(String topic) {
return findByField(Feed_.pushTopicHash, DigestUtils.sha1Hex(topic)); return findByField(Feed_.pushTopicHash, DigestUtils.sha1Hex(topic));
} }
public int deleteWithoutSubscriptions(int max) { public List<Feed> findWithoutSubscriptions(int max) {
CriteriaQuery<Feed> query = builder.createQuery(getType()); CriteriaQuery<Feed> query = builder.createQuery(getType());
Root<Feed> root = query.from(getType()); Root<Feed> root = query.from(getType());
@@ -105,54 +101,6 @@ public class FeedDAO extends GenericDAO<Feed> {
TypedQuery<Feed> q = em.createQuery(query); TypedQuery<Feed> q = em.createQuery(query);
q.setMaxResults(max); q.setMaxResults(max);
List<Feed> list = q.getResultList(); return q.getResultList();
int deleted = list.size();
delete(list);
return deleted;
}
public static enum DuplicateMode {
NORMALIZED_URL(Feed_.normalizedUrlHash), LAST_CONTENT(Feed_.lastContentHash), PUSH_TOPIC(Feed_.pushTopicHash);
private SingularAttribute<Feed, String> path;
private DuplicateMode(SingularAttribute<Feed, String> path) {
this.path = path;
}
public SingularAttribute<Feed, String> getPath() {
return path;
}
}
public List<FeedCount> findDuplicates(DuplicateMode mode, int offset, int limit, long minCount) {
CriteriaQuery<String> query = builder.createQuery(String.class);
Root<Feed> root = query.from(getType());
Path<String> path = root.get(mode.getPath());
Expression<Long> count = builder.count(path);
query.select(path);
query.groupBy(path);
query.having(builder.greaterThan(count, minCount));
TypedQuery<String> q = em.createQuery(query);
limit(q, offset, limit);
List<String> pathValues = q.getResultList();
List<FeedCount> result = Lists.newArrayList();
for (String pathValue : pathValues) {
FeedCount fc = new FeedCount(pathValue);
for (Feed feed : findByField(mode.getPath(), pathValue)) {
Feed f = new Feed();
f.setId(feed.getId());
f.setUrl(feed.getUrl());
fc.getFeeds().add(f);
}
result.add(fc);
}
return result;
} }
} }

View File

@@ -2,6 +2,7 @@ package com.commafeed.backend.dao;
import java.util.List; import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.TypedQuery; import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join; import javax.persistence.criteria.Join;
@@ -15,6 +16,7 @@ import com.commafeed.backend.model.FeedEntryContent_;
import com.commafeed.backend.model.FeedEntry_; import com.commafeed.backend.model.FeedEntry_;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
@Stateless
public class FeedEntryContentDAO extends GenericDAO<FeedEntryContent> { public class FeedEntryContentDAO extends GenericDAO<FeedEntryContent> {
public Long findExisting(String contentHash, String titleHash) { public Long findExisting(String contentHash, String titleHash) {
@@ -44,6 +46,7 @@ public class FeedEntryContentDAO extends GenericDAO<FeedEntryContent> {
List<FeedEntryContent> list = q.getResultList(); List<FeedEntryContent> list = q.getResultList();
int deleted = list.size(); int deleted = list.size();
delete(list);
return deleted; return deleted;
} }

View File

@@ -11,6 +11,7 @@ import javax.persistence.criteria.Root;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.DigestUtils;
import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedEntry;
import com.commafeed.backend.model.FeedEntry_; import com.commafeed.backend.model.FeedEntry_;
import com.commafeed.backend.model.Feed_; import com.commafeed.backend.model.Feed_;
@@ -36,6 +37,21 @@ public class FeedEntryDAO extends GenericDAO<FeedEntry> {
return Iterables.getFirst(list, null); return Iterables.getFirst(list, null);
} }
public int delete(Feed feed, int max) {
CriteriaQuery<FeedEntry> query = builder.createQuery(getType());
Root<FeedEntry> root = query.from(getType());
query.where(builder.equal(root.get(FeedEntry_.feed), feed));
TypedQuery<FeedEntry> q = em.createQuery(query);
q.setMaxResults(max);
List<FeedEntry> list = q.getResultList();
int deleted = list.size();
delete(list);
return deleted;
}
public int delete(Date olderThan, int max) { public int delete(Date olderThan, int max) {
CriteriaQuery<FeedEntry> query = builder.createQuery(getType()); CriteriaQuery<FeedEntry> query = builder.createQuery(getType());
Root<FeedEntry> root = query.from(getType()); Root<FeedEntry> root = query.from(getType());

View File

@@ -15,8 +15,9 @@ import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root; import javax.persistence.criteria.Root;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.builder.CompareToBuilder;
import org.hibernate.Criteria; import org.hibernate.Criteria;
import org.hibernate.criterion.Conjunction;
import org.hibernate.criterion.Disjunction; import org.hibernate.criterion.Disjunction;
import org.hibernate.criterion.MatchMode; import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order; import org.hibernate.criterion.Order;
@@ -31,6 +32,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;
@@ -40,31 +43,34 @@ import com.commafeed.backend.services.ApplicationSettingsService;
import com.commafeed.frontend.model.UnreadCount; import com.commafeed.frontend.model.UnreadCount;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
@Stateless @Stateless
public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> { 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
public int compare(FeedEntryStatus o1, FeedEntryStatus o2) { public int compare(FeedEntryStatus o1, FeedEntryStatus o2) {
return ObjectUtils.compare(o2.getEntryUpdated(), o1.getEntryUpdated()); CompareToBuilder builder = new CompareToBuilder();
builder.append(o2.getEntryUpdated(), o1.getEntryUpdated());
builder.append(o2.getId(), o1.getId());
return builder.build();
}; };
}; };
private static final Comparator<FeedEntryStatus> STATUS_COMPARATOR_ASC = new Comparator<FeedEntryStatus>() { private static final Comparator<FeedEntryStatus> STATUS_COMPARATOR_ASC = Ordering.from(STATUS_COMPARATOR_DESC).reverse();
@Override
public int compare(FeedEntryStatus o1, FeedEntryStatus o2) {
return ObjectUtils.compare(o1.getEntryUpdated(), o2.getEntryUpdated());
};
};
@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());
@@ -77,14 +83,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 {
@@ -93,6 +99,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());
@@ -102,11 +114,12 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
predicates.add(builder.equal(root.get(FeedEntryStatus_.user), user)); predicates.add(builder.equal(root.get(FeedEntryStatus_.user), user));
predicates.add(builder.equal(root.get(FeedEntryStatus_.starred), true)); predicates.add(builder.equal(root.get(FeedEntryStatus_.starred), true));
query.where(predicates.toArray(new Predicate[0]));
if (newerThan != null) { if (newerThan != null) {
predicates.add(builder.greaterThanOrEqualTo(root.get(FeedEntryStatus_.entryInserted), newerThan)); predicates.add(builder.greaterThanOrEqualTo(root.get(FeedEntryStatus_.entryInserted), newerThan));
} }
query.where(predicates.toArray(new Predicate[0]));
orderStatusesBy(query, root, order); orderStatusesBy(query, root, order);
@@ -115,13 +128,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(User user, 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()));
@@ -139,7 +153,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()));
@@ -152,6 +166,13 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
} }
} }
if (tag != null) {
Conjunction and = Restrictions.conjunction();
and.add(Restrictions.eq(FeedEntryTag_.user.getName(), user));
and.add(Restrictions.eq(FeedEntryTag_.name.getName(), tag));
criteria.createCriteria(FeedEntry_.tags.getName(), ALIAS_TAG, JoinType.INNER_JOIN, and);
}
if (newerThan != null) { if (newerThan != null) {
criteria.add(Restrictions.ge(FeedEntry_.inserted.getName(), newerThan)); criteria.add(Restrictions.ge(FeedEntry_.inserted.getName(), newerThan));
} }
@@ -165,13 +186,11 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
} }
if (order != null) { if (order != null) {
Order o = null;
if (order == ReadingOrder.asc) { if (order == ReadingOrder.asc) {
o = Order.asc(FeedEntry_.updated.getName()); criteria.addOrder(Order.asc(FeedEntry_.updated.getName())).addOrder(Order.asc(FeedEntry_.id.getName()));
} else { } else {
o = Order.desc(FeedEntry_.updated.getName()); criteria.addOrder(Order.desc(FeedEntry_.updated.getName())).addOrder(Order.desc(FeedEntry_.id.getName()));
} }
criteria.addOrder(o);
} }
if (offset > -1) { if (offset > -1) {
criteria.setFirstResult(offset); criteria.setFirstResult(offset);
@@ -188,14 +207,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(user, 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");
@@ -237,7 +256,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);
} }
@@ -245,9 +267,9 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public UnreadCount getUnreadCount(FeedSubscription subscription) { public UnreadCount getUnreadCount(User user, FeedSubscription subscription) {
UnreadCount uc = null; UnreadCount uc = null;
Criteria criteria = buildSearchCriteria(subscription, true, null, null, -1, -1, null, null); Criteria criteria = buildSearchCriteria(user, 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");
@@ -273,15 +295,15 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
} }
private void orderStatusesBy(CriteriaQuery<?> query, Path<FeedEntryStatus> statusJoin, ReadingOrder order) { private void orderStatusesBy(CriteriaQuery<?> query, Path<FeedEntryStatus> statusJoin, ReadingOrder order) {
orderBy(query, statusJoin.get(FeedEntryStatus_.entryUpdated), order); orderBy(query, statusJoin.get(FeedEntryStatus_.entryUpdated), statusJoin.get(FeedEntryStatus_.id), order);
} }
private void orderBy(CriteriaQuery<?> query, Path<Date> date, ReadingOrder order) { private void orderBy(CriteriaQuery<?> query, Path<Date> date, Path<Long> id, ReadingOrder order) {
if (order != null) { if (order != null) {
if (order == ReadingOrder.asc) { if (order == ReadingOrder.asc) {
query.orderBy(builder.asc(date)); query.orderBy(builder.asc(date), builder.asc(id));
} else { } else {
query.orderBy(builder.desc(date)); query.orderBy(builder.desc(date), builder.desc(id));
} }
} }
} }

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

@@ -63,10 +63,11 @@ public abstract class GenericDAO<T extends AbstractModel> {
} }
} }
public void delete(Collection<? extends AbstractModel> objects) { public int delete(Collection<? extends AbstractModel> objects) {
for (AbstractModel object : objects) { for (AbstractModel object : objects) {
delete(object); delete(object);
} }
return objects.size();
} }
public void deleteById(Long id) { public void deleteById(Long id) {

View File

@@ -50,6 +50,8 @@ public class FeedFetcher {
result = getter.getBinary(extractedUrl, lastModified, eTag, timeout); result = getter.getBinary(extractedUrl, lastModified, eTag, timeout);
content = result.getContent(); content = result.getContent();
fetchedFeed = parser.parse(feedUrl, content); fetchedFeed = parser.parse(feedUrl, content);
} else {
throw e;
} }
} else { } else {
throw e; throw e;

View File

@@ -82,7 +82,7 @@ public class FeedParser {
continue; continue;
} }
entry.setGuid(FeedUtils.truncate(guid, 2048)); entry.setGuid(FeedUtils.truncate(guid, 2048));
entry.setUrl(FeedUtils.truncate(FeedUtils.toAbsoluteUrl(item.getLink(), feed.getLink()), 2048)); entry.setUrl(FeedUtils.truncate(FeedUtils.toAbsoluteUrl(item.getLink(), feed.getLink(), feed.getUrlAfterRedirect()), 2048));
entry.setUpdated(validateDate(getEntryUpdateDate(item), true)); entry.setUpdated(validateDate(getEntryUpdateDate(item), true));
FeedEntryContent content = new FeedEntryContent(); FeedEntryContent content = new FeedEntryContent();

View File

@@ -8,11 +8,11 @@ import com.commafeed.backend.model.FeedEntry;
public class FeedRefreshContext { public class FeedRefreshContext {
private Feed feed; private Feed feed;
private List<FeedEntry> entries; private List<FeedEntry> entries;
private boolean isUrgent; private boolean urgent;
public FeedRefreshContext(Feed feed, boolean isUrgent) { public FeedRefreshContext(Feed feed, boolean isUrgent) {
this.feed = feed; this.feed = feed;
this.isUrgent = isUrgent; this.urgent = isUrgent;
} }
public Feed getFeed() { public Feed getFeed() {
@@ -24,11 +24,11 @@ public class FeedRefreshContext {
} }
public boolean isUrgent() { public boolean isUrgent() {
return isUrgent; return urgent;
} }
public void setUrgent(boolean isUrgent) { public void setUrgent(boolean urgent) {
this.isUrgent = isUrgent; this.urgent = urgent;
} }
public List<FeedEntry> getEntries() { public List<FeedEntry> getEntries() {

View File

@@ -17,6 +17,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.time.DateUtils; import org.apache.commons.lang.time.DateUtils;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Meter; import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.MetricRegistry;
import com.commafeed.backend.dao.FeedDAO; import com.commafeed.backend.dao.FeedDAO;
@@ -66,6 +67,24 @@ public class FeedRefreshTaskGiver {
feedRefreshed = metrics.meter(MetricRegistry.name(getClass(), "feedRefreshed")); feedRefreshed = metrics.meter(MetricRegistry.name(getClass(), "feedRefreshed"));
threadWaited = metrics.meter(MetricRegistry.name(getClass(), "threadWaited")); threadWaited = metrics.meter(MetricRegistry.name(getClass(), "threadWaited"));
refill = metrics.meter(MetricRegistry.name(getClass(), "refill")); refill = metrics.meter(MetricRegistry.name(getClass(), "refill"));
metrics.register(MetricRegistry.name(getClass(), "addQueue"), new Gauge<Integer>() {
@Override
public Integer getValue() {
return addQueue.size();
}
});
metrics.register(MetricRegistry.name(getClass(), "takeQueue"), new Gauge<Integer>() {
@Override
public Integer getValue() {
return takeQueue.size();
}
});
metrics.register(MetricRegistry.name(getClass(), "giveBackQueue"), new Gauge<Integer>() {
@Override
public Integer getValue() {
return giveBackQueue.size();
}
});
} }
@PreDestroy @PreDestroy
@@ -147,22 +166,25 @@ public class FeedRefreshTaskGiver {
*/ */
private void refill() { private void refill() {
refill.mark(); refill.mark();
int count = Math.min(100, 3 * backgroundThreads);
// first, get feeds that are up to refresh from the database
List<FeedRefreshContext> contexts = Lists.newArrayList(); List<FeedRefreshContext> contexts = Lists.newArrayList();
if (!applicationSettingsService.get().isCrawlingPaused()) { int batchSize = Math.min(100, 3 * backgroundThreads);
List<Feed> feeds = feedDAO.findNextUpdatable(count, getLastLoginThreshold());
for (Feed feed : feeds) { // add feeds we got from the add() method
contexts.add(new FeedRefreshContext(feed, false)); int addQueueSize = addQueue.size();
} for (int i = 0; i < Math.min(batchSize, addQueueSize); i++) {
contexts.add(addQueue.poll());
} }
// then, add to those the feeds we got from the add() method. We add them at the beginning of the list as they probably have a // add feeds that are up to refresh from the database
// higher priority if (!applicationSettingsService.get().isCrawlingPaused()) {
int size = addQueue.size(); int count = batchSize - contexts.size();
for (int i = 0; i < size; i++) { if (count > 0) {
contexts.add(0, addQueue.poll()); List<Feed> feeds = feedDAO.findNextUpdatable(count, getLastLoginThreshold());
for (Feed feed : feeds) {
contexts.add(new FeedRefreshContext(feed, false));
}
}
} }
// set the disabledDate to now as we use the disabledDate in feedDAO to decide what to refresh next. We also use a map to remove // set the disabledDate to now as we use the disabledDate in feedDAO to decide what to refresh next. We also use a map to remove
@@ -178,8 +200,8 @@ public class FeedRefreshTaskGiver {
takeQueue.addAll(map.values()); takeQueue.addAll(map.values());
// add feeds from the giveBack queue to the map, overriding duplicates // add feeds from the giveBack queue to the map, overriding duplicates
size = giveBackQueue.size(); int giveBackQueueSize = giveBackQueue.size();
for (int i = 0; i < size; i++) { for (int i = 0; i < giveBackQueueSize; i++) {
Feed feed = giveBackQueue.poll(); Feed feed = giveBackQueue.poll();
map.put(feed.getId(), new FeedRefreshContext(feed, false)); map.put(feed.getId(), new FeedRefreshContext(feed, false));
} }

View File

@@ -1,6 +1,8 @@
package com.commafeed.backend.feeds; package com.commafeed.backend.feeds;
import java.io.StringReader; import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
@@ -15,6 +17,7 @@ import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.math.stat.descriptive.SummaryStatistics; import org.apache.commons.math.stat.descriptive.SummaryStatistics;
import org.apache.wicket.request.UrlUtils;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import org.jsoup.nodes.Document.OutputSettings; import org.jsoup.nodes.Document.OutputSettings;
@@ -389,17 +392,37 @@ public class FeedUtils {
return url; return url;
} }
public static String toAbsoluteUrl(String url, String baseUrl) { /**
*
* @param url
* the url of the entry
* @param feedLink
* the url of the feed as described in the feed
* @param feedUrl
* the url of the feed that we used to fetch the feed
* @return an absolute url pointing to the entry
*/
public static String toAbsoluteUrl(String url, String feedLink, String feedUrl) {
url = StringUtils.trimToNull(StringUtils.normalizeSpace(url)); url = StringUtils.trimToNull(StringUtils.normalizeSpace(url));
if (baseUrl == null || url == null || url.startsWith("http")) { if (url == null || url.startsWith("http")) {
return url; return url;
} }
if (url.startsWith("/") == false) { String baseUrl = (feedLink == null || UrlUtils.isRelative(feedLink)) ? feedUrl : feedLink;
url = "/" + url;
if (baseUrl == null) {
return url;
} }
return baseUrl + url; String result = null;
try {
result = new URL(new URL(baseUrl), url).toString();
} catch (MalformedURLException e) {
log.debug("could not parse url : " + e.getMessage(), e);
result = url;
}
return result;
} }
public static String getFaviconUrl(FeedSubscription subscription, String publicUrl) { public static String getFaviconUrl(FeedSubscription subscription, String publicUrl) {

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;
@@ -8,6 +9,8 @@ import javax.persistence.Entity;
import javax.persistence.FetchType; import javax.persistence.FetchType;
import javax.persistence.JoinColumn; import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne; import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table; import javax.persistence.Table;
import javax.persistence.Temporal; import javax.persistence.Temporal;
import javax.persistence.TemporalType; import javax.persistence.TemporalType;
@@ -19,6 +22,8 @@ import lombok.Setter;
import org.hibernate.annotations.Cache; import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.annotations.CacheConcurrencyStrategy;
import com.google.common.collect.Lists;
@Entity @Entity
@Table(name = "FEEDENTRYSTATUSES") @Table(name = "FEEDENTRYSTATUSES")
@SuppressWarnings("serial") @SuppressWarnings("serial")
@@ -26,6 +31,7 @@ import org.hibernate.annotations.CacheConcurrencyStrategy;
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL) @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
@Getter @Getter
@Setter @Setter
@NamedQueries(@NamedQuery(name="Statuses.deleteOld", query="delete from FeedEntryStatus s where s.entryInserted < :date and s.starred = false"))
public class FeedEntryStatus extends AbstractModel { public class FeedEntryStatus extends AbstractModel {
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@@ -42,6 +48,9 @@ public class FeedEntryStatus extends AbstractModel {
@Transient @Transient
private boolean markable; private boolean markable;
@Transient
private List<FeedEntryTag> tags = Lists.newArrayList();
/** /**
* 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

@@ -32,7 +32,7 @@ public class User extends AbstractModel {
@Column(length = 32, nullable = false, unique = true) @Column(length = 32, nullable = false, unique = true)
private String name; private String name;
@Column(length = 255, unique = true) @Column(length = 255, unique = true)
private String email; private String email;
@@ -66,4 +66,8 @@ public class User extends AbstractModel {
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE) @OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
private Set<FeedSubscription> subscriptions; private Set<FeedSubscription> subscriptions;
@Column(name = "last_full_refresh")
@Temporal(TemporalType.TIMESTAMP)
private Date lastFullRefresh;
} }

View File

@@ -68,4 +68,7 @@ public class UserSettings extends AbstractModel {
@Column(length = Integer.MAX_VALUE) @Column(length = Integer.MAX_VALUE)
private String customCss; private String customCss;
@Column(name = "scroll_speed")
private int scrollSpeed;
} }

View File

@@ -9,13 +9,14 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpHeaders; import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair; import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicNameValuePair; import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils; import org.apache.http.util.EntityUtils;
import org.apache.wicket.util.io.IOUtils;
import com.commafeed.backend.HttpGetter; import com.commafeed.backend.HttpGetter;
import com.commafeed.backend.feeds.FeedRefreshTaskGiver; import com.commafeed.backend.feeds.FeedRefreshTaskGiver;
@@ -67,10 +68,11 @@ public class SubscriptionHandler {
post.setHeader(HttpHeaders.USER_AGENT, "CommaFeed"); post.setHeader(HttpHeaders.USER_AGENT, "CommaFeed");
post.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED); post.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED);
HttpClient client = HttpGetter.newClient(20000); CloseableHttpClient client = HttpGetter.newClient(20000);
CloseableHttpResponse response = null;
try { try {
post.setEntity(new UrlEncodedFormEntity(nvp)); post.setEntity(new UrlEncodedFormEntity(nvp));
HttpResponse response = client.execute(post); response = client.execute(post);
int code = response.getStatusLine().getStatusCode(); int code = response.getStatusLine().getStatusCode();
if (code != 204 && code != 202 && code != 200) { if (code != 204 && code != 202 && code != 200) {
@@ -90,7 +92,8 @@ public class SubscriptionHandler {
} catch (Exception e) { } catch (Exception e) {
log.error("Could not subscribe to {} for {} : " + e.getMessage(), hub, topic); log.error("Could not subscribe to {} for {} : " + e.getMessage(), hub, topic);
} finally { } finally {
client.getConnectionManager().shutdown(); IOUtils.closeQuietly(response);
IOUtils.closeQuietly(client);
} }
} }
} }

View File

@@ -25,6 +25,8 @@ import com.commafeed.backend.model.FeedSubscription;
*/ */
@Slf4j @Slf4j
public class DatabaseCleaningService { public class DatabaseCleaningService {
private static final int BATCH_SIZE = 100;
@Inject @Inject
FeedDAO feedDAO; FeedDAO feedDAO;
@@ -44,12 +46,29 @@ public class DatabaseCleaningService {
@Inject @Inject
ApplicationSettingsService applicationSettingsService; ApplicationSettingsService applicationSettingsService;
public long cleanFeedsWithoutSubscriptions() { public long cleanEntriesForFeedsWithoutSubscriptions() {
log.info("cleaning entries for feeds without subscriptions");
long total = 0; long total = 0;
int deleted = -1; int deleted = 0;
do { do {
deleted = feedDAO.deleteWithoutSubscriptions(10); deleted = 0;
List<Feed> feeds = feedDAO.findWithoutSubscriptions(1);
for (Feed feed : feeds) {
deleted += feedEntryDAO.delete(feed, BATCH_SIZE);
total += deleted;
log.info("removed {} entries for feeds without subscriptions", total);
}
} while (deleted != 0);
log.info("cleanup done: {} entries for feeds without subscriptions deleted", total);
return total;
}
public long cleanFeedsWithoutSubscriptions() {
log.info("cleaning feeds without subscriptions");
long total = 0;
int deleted = 0;
do {
deleted = feedDAO.delete(feedDAO.findWithoutSubscriptions(1));
total += deleted; total += deleted;
log.info("removed {} feeds without subscriptions", total); log.info("removed {} feeds without subscriptions", total);
} while (deleted != 0); } while (deleted != 0);
@@ -58,15 +77,15 @@ public class DatabaseCleaningService {
} }
public long cleanContentsWithoutEntries() { public long cleanContentsWithoutEntries() {
log.info("cleaning contents without entries");
long total = 0; long total = 0;
int deleted = -1; int deleted = 0;
do { do {
deleted = feedEntryContentDAO.deleteWithoutEntries(10); deleted = feedEntryContentDAO.deleteWithoutEntries(BATCH_SIZE);
total += deleted; total += deleted;
log.info("removed {} feeds without subscriptions", total); log.info("removed {} contents without entries", total);
} while (deleted != 0); } while (deleted != 0);
log.info("cleanup done: {} feeds without subscriptions deleted", total); log.info("cleanup done: {} contents without entries deleted", total);
return total; return total;
} }
@@ -75,9 +94,9 @@ public class DatabaseCleaningService {
cal.add(Calendar.MINUTE, -1 * (int) unit.toMinutes(value)); cal.add(Calendar.MINUTE, -1 * (int) unit.toMinutes(value));
long total = 0; long total = 0;
int deleted = -1; int deleted = 0;
do { do {
deleted = feedEntryDAO.delete(cal.getTime(), 100); deleted = feedEntryDAO.delete(cal.getTime(), BATCH_SIZE);
total += deleted; total += deleted;
log.info("removed {} entries", total); log.info("removed {} entries", total);
} while (deleted != 0); } while (deleted != 0);
@@ -105,7 +124,7 @@ public class DatabaseCleaningService {
long total = 0; long total = 0;
List<FeedEntryStatus> list = Collections.emptyList(); List<FeedEntryStatus> list = Collections.emptyList();
do { do {
list = feedEntryStatusDAO.getOldStatuses(olderThan, 100); list = feedEntryStatusDAO.getOldStatuses(olderThan, BATCH_SIZE);
if (!list.isEmpty()) { if (!list.isEmpty()) {
feedEntryStatusDAO.delete(list); feedEntryStatusDAO.delete(list);
total += list.size(); total += list.size();

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

@@ -95,11 +95,19 @@ public class FeedSubscriptionService {
} }
} }
public UnreadCount getUnreadCount(FeedSubscription sub) { public void refreshAll(User user) {
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user);
for (FeedSubscription sub : subs) {
Feed feed = sub.getFeed();
taskGiver.add(feed, true);
}
}
public UnreadCount getUnreadCount(User user, FeedSubscription sub) {
UnreadCount count = cache.getUnreadCount(sub); UnreadCount count = cache.getUnreadCount(sub);
if (count == null) { if (count == null) {
log.debug("unread count cache miss for {}", Models.getId(sub)); log.debug("unread count cache miss for {}", Models.getId(sub));
count = feedEntryStatusDAO.getUnreadCount(sub); count = feedEntryStatusDAO.getUnreadCount(user, sub);
cache.setUnreadCount(sub, count); cache.setUnreadCount(sub, count);
} }
return count; return count;
@@ -109,7 +117,7 @@ public class FeedSubscriptionService {
Map<Long, UnreadCount> map = Maps.newHashMap(); Map<Long, UnreadCount> map = Maps.newHashMap();
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user); List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user);
for (FeedSubscription sub : subs) { for (FeedSubscription sub : subs) {
map.put(sub.getId(), getUnreadCount(sub)); map.put(sub.getId(), getUnreadCount(user, sub));
} }
return map; return map;
} }

View File

@@ -41,6 +41,9 @@ public class UserService {
@Inject @Inject
ApplicationSettingsService applicationSettingsService; ApplicationSettingsService applicationSettingsService;
@Inject
FeedSubscriptionService feedSubscriptionService;
public User login(String name, String password) { public User login(String name, String password) {
if (name == null || password == null) { if (name == null || password == null) {
return null; return null;
@@ -52,10 +55,21 @@ public class UserService {
if (authenticated) { if (authenticated) {
Date lastLogin = user.getLastLogin(); Date lastLogin = user.getLastLogin();
Date now = new Date(); Date now = new Date();
boolean saveUser = false;
// only update lastLogin field every hour in order to not // only update lastLogin field every hour in order to not
// invalidate the cache everytime someone logs in // invalidate the cache everytime someone logs in
if (lastLogin == null || lastLogin.before(DateUtils.addHours(now, -1))) { if (lastLogin == null || lastLogin.before(DateUtils.addHours(now, -1))) {
user.setLastLogin(now); user.setLastLogin(now);
saveUser = true;
}
if (applicationSettingsService.get().isHeavyLoad()
&& (user.getLastFullRefresh() == null || user.getLastFullRefresh().before(DateUtils.addMinutes(now, -30)))) {
user.setLastFullRefresh(now);
saveUser = true;
feedSubscriptionService.refreshAll(user);
}
if (saveUser) {
userDAO.saveOrUpdate(user); userDAO.saveOrUpdate(user);
} }
return user; return user;

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

@@ -38,5 +38,8 @@ public class Settings implements Serializable {
@ApiProperty(value = "user's custom css for the website") @ApiProperty(value = "user's custom css for the website")
private String customCss; private String customCss;
@ApiProperty(value = "user's preferred scroll speed when navigating between entries")
private int scrollSpeed;
} }

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

@@ -1,45 +1,54 @@
<!DOCTYPE html> <!DOCTYPE html>
<html xmlns:wicket="http://wicket.apache.org" wicket:id="html"> <html xmlns:wicket="http://wicket.apache.org" wicket:id="html">
<head> <head>
<title>CommaFeed</title> <title>CommaFeed</title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico" /> <link rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
<link rel="apple-touch-icon" href="app-icon-57.png" /> <link rel="apple-touch-icon" href="app-icon-57.png" />
<link rel="apple-touch-icon" sizes="72x72" href="app-icon-72.png" /> <link rel="apple-touch-icon" sizes="72x72" href="app-icon-72.png" />
<link rel="apple-touch-icon" sizes="114x114" href="app-icon-114.png" /> <link rel="apple-touch-icon" sizes="114x114" href="app-icon-114.png" />
<link rel="apple-touch-icon" sizes="144x144" href="app-icon-144.png" /> <link rel="apple-touch-icon" sizes="144x144" href="app-icon-144.png" />
<link rel="icon" sizes="32x32" href="app-icon-32.png" /> <link rel="icon" sizes="32x32" href="app-icon-32.png" />
<link rel="icon" sizes="64x64" href="app-icon-64.png" /> <link rel="icon" sizes="64x64" href="app-icon-64.png" />
<link rel="icon" sizes="128x128" href="app-icon-128.png" /> <link rel="icon" sizes="128x128" href="app-icon-128.png" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico" /> <link rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
<meta name="application-name" content="CommaFeed" /> <meta name="application-name" content="CommaFeed" />
<meta name="msapplication-navbutton-color" content="#F88A14" /> <meta name="msapplication-navbutton-color" content="#F88A14" />
<meta name="msapplication-starturl" content="/" /> <meta name="msapplication-starturl" content="/" />
<meta name="msapplication-square70x70logo" content="metro-icon-70.png"/> <meta name="msapplication-square70x70logo" content="metro-icon-70.png" />
<meta name="msapplication-square150x150logo" content="metro-icon-150.png"/> <meta name="msapplication-square150x150logo" content="metro-icon-150.png" />
<link rel="fluid-icon" href="app-icon-512.png" title="CommaFeed" /> <link rel="fluid-icon" href="app-icon-512.png" title="CommaFeed" />
<link rel="logo" type="image/svg" href="app-icon.svg" /> <link rel="logo" type="image/svg" href="app-icon.svg" />
</head> </head>
<body> <body>
<wicket:child /> <wicket:child />
<wicket:container wicket:id="footer-container"/> <wicket:container wicket:id="footer-container" />
<wicket:container wicket:id="uservoice"> <wicket:container wicket:id="uservoice">
<script>(function(){var uv=document.createElement('script');uv.type='text/javascript';uv.async=true;uv.src='//widget.uservoice.com/XYpTZZteqS4lHvgrTXeA.js';var s=document.getElementsByTagName('script')[0];s.parentNode.insertBefore(uv,s)})()</script> <script>
(function() {
var uv = document.createElement('script');
uv.type = 'text/javascript';
uv.async = true;
uv.src = '//widget.uservoice.com/XYpTZZteqS4lHvgrTXeA.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(uv, s);
})();
</script>
<script> <script>
UserVoice = window.UserVoice || []; UserVoice = window.UserVoice || [];
UserVoice.push(['showTab', 'classic_widget', { UserVoice.push(['showTab', 'classic_widget', {
mode: 'full', mode : 'full',
default_mode: 'feedback', default_mode : 'feedback',
primary_color: '#000', primary_color : '#000',
link_color: '#007dbf', link_color : '#007dbf',
forum_id: 204509, forum_id : 204509,
support_tab_name: 'Contact', support_tab_name : 'Contact',
feedback_tab_name: 'Feedback', feedback_tab_name : 'Feedback',
tab_label: 'Feedback', tab_label : 'Feedback',
tab_color: '#7e72db', tab_color : '#7e72db',
tab_position: 'bottom-right', tab_position : 'bottom-right',
tab_inverted: false tab_inverted : false
}]); }]);
</script> </script>
</wicket:container> </wicket:container>

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

@@ -5,15 +5,19 @@
<div class="text-center"> <div class="text-center">
<img src="images/logo_2.png" /> <img src="images/logo_2.png" />
<div wicket:id="feedback"></div> <div wicket:id="feedback"></div>
<form wicket:id="form"> <form wicket:id="form" class="form-horizontal">
New Password: <div class="form-group">
<input type="password" wicket:id="password" /> <label>New Password</label>
<br /> <input type="password" wicket:id="password" />
Confirm: </div>
<input type="password" wicket:id="confirm" /> <div class="form-group">
<br /> <label>Confirm</label>
<input type="submit" class="btn btn-primary" value="Submit" /> <input type="password" wicket:id="confirm" />
<input type="button" class="btn" wicket:id="cancel" value="Home page" /> </div>
<div class="form-group">
<input type="submit" class="btn btn-primary" value="Submit" />
<input type="button" class="btn btn-default" wicket:id="cancel" value="Home page" />
</div>
</form> </form>
</div> </div>
</div> </div>

View File

@@ -2,15 +2,18 @@
<body> <body>
<wicket:extend> <wicket:extend>
<div class="container"> <div class="container">
<div class="text-center"> <div class="col-xs-6 col-xs-offset-3 text-center">
<img src="images/logo_2.png" /> <img src="images/logo_2.png" />
<div wicket:id="feedback"></div> <div wicket:id="feedback"></div>
<form wicket:id="form"> <form wicket:id="form" class="form-horizontal">
Email: <div class="form-group">
<input type="email" wicket:id="email" /> <label>Email</label>
<br /> <input type="email" wicket:id="email" class="form-control"/>
<input type="submit" class="btn btn-primary" value="Submit" /> </div>
<input type="button" class="btn" wicket:id="cancel" value="Cancel" /> <div class="form-group">
<input type="submit" class="btn btn-primary" value="Submit" />
<input type="button" class="btn btn-default" wicket:id="cancel" value="Cancel" />
</div>
</form> </form>
</div> </div>
</div> </div>

View File

@@ -3,8 +3,8 @@
<body> <body>
<wicket:extend> <wicket:extend>
<div class="welcome"> <div class="welcome">
<div class="container-fluid"> <div class="container">
<div class="row-fluid header"> <div class="row header">
<div class="pull-left"> <div class="pull-left">
<a wicket:id="logo-link"> <a wicket:id="logo-link">
<img src="images/logo_2.png"></img> <img src="images/logo_2.png"></img>
@@ -23,14 +23,14 @@
</a> </a>
</div> </div>
</div> </div>
<div class="row-fluid"> <div class="row">
<div class="span6"> <div class="col-md-6">
<div class="well" id="login-panel"> <div class="well" id="login-panel">
<h3>Login</h3> <h3>Login</h3>
<span wicket:id="login"></span> <span wicket:id="login"></span>
</div> </div>
</div> </div>
<div class="span6" wicket:enclosure="register"> <div class="col-md-6" wicket:enclosure="register">
<div class="well" id="register-panel"> <div class="well" id="register-panel">
<h3>Register</h3> <h3>Register</h3>
<span wicket:id="register"></span> <span wicket:id="register"></span>
@@ -40,7 +40,7 @@
<hr /> <hr />
<div class="footer"> <div class="footer">
<div class="row-fluid"> <div class="row">
<span> <span>
&copy; &copy;
<a href="http://www.commafeed.com" target="_blank">CommaFeed</a> <a href="http://www.commafeed.com" target="_blank">CommaFeed</a>

View File

@@ -4,24 +4,20 @@
<wicket:panel> <wicket:panel>
<span wicket:id="feedback"></span> <span wicket:id="feedback"></span>
<form wicket:id="signInForm"> <form wicket:id="signInForm">
<div class="control-group"> <div class="form-group">
<label class="control-label" for="username">User Name</label> <label for="username">User Name</label>
<div class="controls"> <input type="text" id="username" wicket:id="username" class="form-control"></input>
<input type="text" id="username" wicket:id="username" class="input-block-level"></input>
</div>
</div> </div>
<div class="control-group"> <div class="form-group">
<label class="control-label" for="password">Password</label> <label for="password">Password</label>
<div class="controls"> <input type="password" id="password" wicket:id="password" class="form-control"></input>
<input type="password" id="password" wicket:id="password" class="input-block-level"></input>
</div>
</div> </div>
<p class="help-block" wicket:id="rememberMeRow"> <div wicket:id="rememberMeRow">
<label class="checkbox"> <label>
<input wicket:id="rememberMe" type="checkbox" /> <input wicket:id="rememberMe" type="checkbox" />
Remember me Remember me
</label> </label>
</p> </div>
<div> <div>
<input type="submit" class="btn btn-primary" value="Log in" /> <input type="submit" class="btn btn-primary" value="Log in" />
<a wicket:id="recover" class="pull-right">Forgot password?</a> <a wicket:id="recover" class="pull-right">Forgot password?</a>

View File

@@ -4,23 +4,17 @@
<wicket:panel> <wicket:panel>
<div wicket:id="feedback"></div> <div wicket:id="feedback"></div>
<form wicket:id="form" autocomplete="off"> <form wicket:id="form" autocomplete="off">
<div class="control-group"> <div class="form-group">
<label class="control-label">User Name</label> <label>User Name</label>
<div class="controls"> <input type="text" wicket:id="name" class="form-control"></input>
<input type="text" wicket:id="name" class="input-block-level"></input>
</div>
</div> </div>
<div class="control-group"> <div class="form-group">
<label class="control-label">Password</label> <label>Password</label>
<div class="controls"> <input type="password" wicket:id="password" class="form-control"></input>
<input type="password" wicket:id="password" class="input-block-level"></input>
</div>
</div> </div>
<div class="control-group"> <div class="form-group">
<label class="control-label">Email address (used for password recovery only)</label> <label>Email address (used for password recovery only)</label>
<div class="controls"> <input type="email" wicket:id="email" class="form-control"></input>
<input type="email" wicket:id="email" class="input-block-level"></input>
</div>
</div> </div>
<div> <div>
<input type="submit" class="btn btn-primary" value="Register" /> <input type="submit" class="btn btn-primary" value="Register" />

View File

@@ -146,4 +146,5 @@ public abstract class AbstractREST {
boolean authorized = roles.hasAnyRole(new Roles(requiredRole.name())); boolean authorized = roles.hasAnyRole(new Roles(requiredRole.name()));
return authorized; return authorized;
} }
} }

View File

@@ -1,6 +1,5 @@
package com.commafeed.frontend.rest.resources; package com.commafeed.frontend.rest.resources;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -19,14 +18,12 @@ import org.apache.commons.lang.StringUtils;
import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.MetricRegistry;
import com.commafeed.backend.dao.FeedDAO; import com.commafeed.backend.dao.FeedDAO;
import com.commafeed.backend.dao.FeedDAO.DuplicateMode;
import com.commafeed.backend.dao.UserDAO; import com.commafeed.backend.dao.UserDAO;
import com.commafeed.backend.dao.UserRoleDAO; import com.commafeed.backend.dao.UserRoleDAO;
import com.commafeed.backend.feeds.FeedRefreshTaskGiver; import com.commafeed.backend.feeds.FeedRefreshTaskGiver;
import com.commafeed.backend.feeds.FeedRefreshUpdater; import com.commafeed.backend.feeds.FeedRefreshUpdater;
import com.commafeed.backend.feeds.FeedRefreshWorker; import com.commafeed.backend.feeds.FeedRefreshWorker;
import com.commafeed.backend.model.ApplicationSettings; import com.commafeed.backend.model.ApplicationSettings;
import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.User; import com.commafeed.backend.model.User;
import com.commafeed.backend.model.UserRole; import com.commafeed.backend.model.UserRole;
import com.commafeed.backend.model.UserRole.Role; import com.commafeed.backend.model.UserRole.Role;
@@ -37,13 +34,10 @@ import com.commafeed.backend.services.PasswordEncryptionService;
import com.commafeed.backend.services.UserService; import com.commafeed.backend.services.UserService;
import com.commafeed.backend.startup.StartupBean; import com.commafeed.backend.startup.StartupBean;
import com.commafeed.frontend.SecurityCheck; import com.commafeed.frontend.SecurityCheck;
import com.commafeed.frontend.model.FeedCount;
import com.commafeed.frontend.model.UserModel; import com.commafeed.frontend.model.UserModel;
import com.commafeed.frontend.model.request.FeedMergeRequest;
import com.commafeed.frontend.model.request.IDRequest; import com.commafeed.frontend.model.request.IDRequest;
import com.commafeed.frontend.rest.PrettyPrint; import com.commafeed.frontend.rest.PrettyPrint;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.wordnik.swagger.annotations.Api; import com.wordnik.swagger.annotations.Api;
@@ -259,36 +253,4 @@ public class AdminREST extends AbstractREST {
map.put("old_entries", cleaner.cleanEntriesOlderThan(days, TimeUnit.DAYS)); map.put("old_entries", cleaner.cleanEntriesOlderThan(days, TimeUnit.DAYS));
return Response.ok(map).build(); return Response.ok(map).build();
} }
@Path("/cleanup/findDuplicateFeeds")
@GET
@ApiOperation(value = "Find duplicate feeds")
public Response findDuplicateFeeds(@QueryParam("mode") DuplicateMode mode, @QueryParam("page") int page,
@QueryParam("limit") int limit, @QueryParam("minCount") long minCount) {
List<FeedCount> list = feedDAO.findDuplicates(mode, limit * page, limit, minCount);
return Response.ok(list).build();
}
@Path("/cleanup/merge")
@POST
@ApiOperation(value = "Merge feeds", notes = "Merge feeds together")
public Response mergeFeeds(@ApiParam(required = true) FeedMergeRequest request) {
Feed into = feedDAO.findById(request.getIntoFeedId());
if (into == null) {
return Response.status(Status.BAD_REQUEST).entity("'into feed' not found").build();
}
List<Feed> feeds = Lists.newArrayList();
for (Long feedId : request.getFeedIds()) {
Feed feed = feedDAO.findById(feedId);
feeds.add(feed);
}
if (feeds.isEmpty()) {
return Response.status(Status.BAD_REQUEST).entity("'from feeds' empty").build();
}
cleaner.mergeFeeds(into, feeds);
return Response.ok().build();
}
} }

View File

@@ -22,8 +22,8 @@ import javax.ws.rs.core.Response.Status;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.ObjectUtils;
import com.commafeed.backend.cache.CacheService; import com.commafeed.backend.cache.CacheService;
import com.commafeed.backend.dao.FeedCategoryDAO; import com.commafeed.backend.dao.FeedCategoryDAO;
@@ -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);
@@ -135,11 +136,11 @@ public class CategoryREST extends AbstractREST {
} }
if (ALL.equals(id)) { if (ALL.equals(id)) {
entries.setName("All"); entries.setName(ObjectUtils.defaultIfNull(tag, "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,16 +193,20 @@ 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 = "all entries or only unread ones", allowableValues = "all,unread", required = true) @DefaultValue("all") @QueryParam("readType") ReadingMode readType,
@ApiParam(value = "only entries newer than this") @QueryParam("newerThan") Long newerThan,
@ApiParam(value = "offset for paging") @DefaultValue("0") @QueryParam("offset") int offset,
@ApiParam(value = "limit for paging, default 20, maximum 1000") @DefaultValue("20") @QueryParam("limit") int limit,
@ApiParam(value = "date ordering", allowableValues = "asc,desc") @QueryParam("order") @DefaultValue("desc") ReadingOrder order,
@ApiParam(
value = "search for keywords in either the title or the content of the entries, separated by spaces, 3 characters minimum") @QueryParam("keywords") String keywords,
@ApiParam(value = "return only entry ids") @DefaultValue("false") @QueryParam("onlyIds") boolean onlyIds,
@ApiParam(value = "comma-separated list of excluded subscription ids") @QueryParam("excludedSubscriptionIds") String excludedSubscriptionIds,
@ApiParam(value = "keep only entries tagged with this tag") @QueryParam("tag") String tag) {
Preconditions.checkNotNull(id); Response response = getCategoryEntries(id, readType, newerThan, offset, limit, order, keywords, onlyIds, excludedSubscriptionIds,
tag);
ReadingMode readType = ReadingMode.all;
ReadingOrder order = ReadingOrder.desc;
int offset = 0;
int limit = 20;
Response response = getCategoryEntries(id, readType, null, offset, limit, order, null, false, null);
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

@@ -71,6 +71,7 @@ import com.commafeed.frontend.model.request.IDRequest;
import com.commafeed.frontend.model.request.MarkRequest; import com.commafeed.frontend.model.request.MarkRequest;
import com.commafeed.frontend.model.request.SubscribeRequest; import com.commafeed.frontend.model.request.SubscribeRequest;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.sun.syndication.feed.opml.Opml; import com.sun.syndication.feed.opml.Opml;
import com.sun.syndication.feed.synd.SyndEntry; import com.sun.syndication.feed.synd.SyndEntry;
@@ -167,8 +168,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(
@@ -195,16 +196,18 @@ public class FeedREST extends AbstractREST {
@ApiOperation(value = "Get feed entries as a feed", notes = "Get a feed of feed entries") @ApiOperation(value = "Get feed entries as a feed", notes = "Get a feed of feed entries")
@Produces(MediaType.APPLICATION_XML) @Produces(MediaType.APPLICATION_XML)
@SecurityCheck(value = Role.USER, apiKeyAllowed = true) @SecurityCheck(value = Role.USER, apiKeyAllowed = true)
public Response getFeedEntriesAsFeed(@ApiParam(value = "id of the feed", required = true) @QueryParam("id") String id) { public Response getFeedEntriesAsFeed(
@ApiParam(value = "id of the feed", required = true) @QueryParam("id") String id,
@ApiParam(value = "all entries or only unread ones", allowableValues = "all,unread", required = true) @DefaultValue("all") @QueryParam("readType") ReadingMode readType,
@ApiParam(value = "only entries newer than this") @QueryParam("newerThan") Long newerThan,
@ApiParam(value = "offset for paging") @DefaultValue("0") @QueryParam("offset") int offset,
@ApiParam(value = "limit for paging, default 20, maximum 1000") @DefaultValue("20") @QueryParam("limit") int limit,
@ApiParam(value = "date ordering", allowableValues = "asc,desc") @QueryParam("order") @DefaultValue("desc") ReadingOrder order,
@ApiParam(
value = "search for keywords in either the title or the content of the entries, separated by spaces, 3 characters minimum") @QueryParam("keywords") String keywords,
@ApiParam(value = "return only entry ids") @DefaultValue("false") @QueryParam("onlyIds") boolean onlyIds) {
Preconditions.checkNotNull(id); Response response = getFeedEntries(id, readType, newerThan, offset, limit, order, keywords, onlyIds);
ReadingMode readType = ReadingMode.all;
ReadingOrder order = ReadingOrder.desc;
int offset = 0;
int limit = 20;
Response response = getFeedEntries(id, readType, null, offset, limit, order, null, false);
if (response.getStatus() != Status.OK.getStatusCode()) { if (response.getStatus() != Status.OK.getStatusCode()) {
return response; return response;
} }
@@ -262,7 +265,8 @@ public class FeedREST extends AbstractREST {
try { try {
info = fetchFeedInternal(req.getUrl()); info = fetchFeedInternal(req.getUrl());
} catch (Exception e) { } catch (Exception e) {
return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build(); return Response.status(Status.INTERNAL_SERVER_ERROR).entity(Throwables.getStackTraceAsString(Throwables.getRootCause(e)))
.build();
} }
return Response.ok(info).build(); return Response.ok(info).build();
} }
@@ -271,11 +275,7 @@ public class FeedREST extends AbstractREST {
@GET @GET
@ApiOperation(value = "Queue all feeds of the user for refresh", notes = "Manually add all feeds of the user to the refresh queue") @ApiOperation(value = "Queue all feeds of the user for refresh", notes = "Manually add all feeds of the user to the refresh queue")
public Response queueAllForRefresh() { public Response queueAllForRefresh() {
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(getUser()); feedSubscriptionService.refreshAll(getUser());
for (FeedSubscription sub : subs) {
Feed feed = sub.getFeed();
taskGiver.add(feed, true);
}
return Response.ok().build(); return Response.ok().build();
} }
@@ -374,8 +374,10 @@ public class FeedREST extends AbstractREST {
try { try {
url = fetchFeedInternal(url).getUrl(); url = fetchFeedInternal(url).getUrl();
FeedCategory category = CategoryREST.ALL.equals(req.getCategoryId()) ? null : feedCategoryDAO.findById(Long.valueOf(req FeedCategory category = null;
.getCategoryId())); if (req.getCategoryId() != null && !CategoryREST.ALL.equals(req.getCategoryId())) {
category = feedCategoryDAO.findById(Long.valueOf(req.getCategoryId()));
}
FeedInfo info = fetchFeedInternal(url); FeedInfo info = fetchFeedInternal(url);
feedSubscriptionService.subscribe(getUser(), info.getUrl(), req.getTitle(), category); feedSubscriptionService.subscribe(getUser(), info.getUrl(), req.getTitle(), category);
} catch (Exception e) { } catch (Exception e) {

View File

@@ -79,6 +79,7 @@ public class UserREST extends AbstractREST {
s.setTheme(settings.getTheme()); s.setTheme(settings.getTheme());
s.setCustomCss(settings.getCustomCss()); s.setCustomCss(settings.getCustomCss());
s.setLanguage(settings.getLanguage()); s.setLanguage(settings.getLanguage());
s.setScrollSpeed(settings.getScrollSpeed());
} else { } else {
s.setReadingMode(ReadingMode.unread.name()); s.setReadingMode(ReadingMode.unread.name());
s.setReadingOrder(ReadingOrder.desc.name()); s.setReadingOrder(ReadingOrder.desc.name());
@@ -88,6 +89,7 @@ public class UserREST extends AbstractREST {
s.setSocialButtons(true); s.setSocialButtons(true);
s.setScrollMarks(true); s.setScrollMarks(true);
s.setLanguage("en"); s.setLanguage("en");
s.setScrollSpeed(400);
} }
return Response.ok(s).build(); return Response.ok(s).build();
} }
@@ -116,6 +118,7 @@ public class UserREST extends AbstractREST {
s.setCustomCss(settings.getCustomCss()); s.setCustomCss(settings.getCustomCss());
s.setSocialButtons(settings.isSocialButtons()); s.setSocialButtons(settings.isSocialButtons());
s.setLanguage(settings.getLanguage()); s.setLanguage(settings.getLanguage());
s.setScrollSpeed(settings.getScrollSpeed());
userSettingsDAO.saveOrUpdate(s); userSettingsDAO.saveOrUpdate(s);
return Response.ok().build(); return Response.ok().build();

View File

@@ -1,45 +0,0 @@
package liquibase.integration.cdi;
import java.sql.SQLException;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.Produces;
import javax.enterprise.inject.spi.AfterBeanDiscovery;
import javax.enterprise.inject.spi.AfterDeploymentValidation;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.Extension;
import javax.sql.DataSource;
import liquibase.integration.cdi.annotations.LiquibaseType;
import liquibase.resource.ResourceAccessor;
/**
* temporary fix until https://liquibase.jira.com/browse/CORE-1325 is fixed
*/
public class CDIBootstrap implements Extension {
void afterBeanDiscovery(@Observes AfterBeanDiscovery abd, BeanManager bm) {
}
void afterDeploymentValidation(@Observes AfterDeploymentValidation event, BeanManager manager) {
}
@Produces
@LiquibaseType
public CDILiquibaseConfig createConfig() {
return null;
}
@Produces
@LiquibaseType
public DataSource createDataSource() throws SQLException {
return null;
}
@Produces
@LiquibaseType
public ResourceAccessor create() {
return null;
}
}

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/persistence/orm
http://java.sun.com/xml/ns/persistence/orm_2_0.xsd">
<named-query name="Statuses.deleteOld">
<query>delete from FeedEntryStatus s where s.entryInserted &lt; :date and s.starred = false</query>
</named-query>
</entity-mappings>

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" <persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=" xsi:schemaLocation="
http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"> http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
@@ -9,6 +8,7 @@
<jta-data-source>${jpa.datasource.name}</jta-data-source> <jta-data-source>${jpa.datasource.name}</jta-data-source>
<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode> <shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
<properties> <properties>
<property name="openejb.jpa.auto-scan" value="true" />
<property name="format_sql" value="true" /> <property name="format_sql" value="true" />
<property name="use_sql_comments" value="true" /> <property name="use_sql_comments" value="true" />
@@ -22,26 +22,17 @@
<property name="hibernate.generate_statistics" value="true" /> <property name="hibernate.generate_statistics" value="true" />
<property name="hibernate.cache.use_second_level_cache" <property name="hibernate.cache.use_second_level_cache" value="${jpa.cache}" />
value="${jpa.cache}" />
<property name="hibernate.cache.use_query_cache" value="${jpa.cache}" /> <property name="hibernate.cache.use_query_cache" value="${jpa.cache}" />
<property name="hibernate.cache.region.factory_class" <property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.infinispan.InfinispanRegionFactory" />
value="org.hibernate.cache.infinispan.InfinispanRegionFactory" /> <property name="hibernate.cache.infinispan.statistics" value="true" />
<property name="hibernate.cache.infinispan.statistics"
value="true" />
<property name="hibernate.cache.infinispan.entity.eviction.strategy" <property name="hibernate.cache.infinispan.entity.eviction.strategy" value="LRU" />
value="LRU" /> <property name="hibernate.cache.infinispan.entity.eviction.wake_up_interval" value="2000" />
<property <property name="hibernate.cache.infinispan.entity.eviction.max_entries" value="10000" />
name="hibernate.cache.infinispan.entity.eviction.wake_up_interval" <property name="hibernate.cache.infinispan.entity.expiration.lifespan" value="60000" />
value="2000" /> <property name="hibernate.cache.infinispan.entity.expiration.max_idle" value="30000" />
<property name="hibernate.cache.infinispan.entity.eviction.max_entries"
value="10000" />
<property name="hibernate.cache.infinispan.entity.expiration.lifespan"
value="60000" />
<property name="hibernate.cache.infinispan.entity.expiration.max_idle"
value="30000" />
</properties> </properties>
</persistence-unit> </persistence-unit>

View File

@@ -357,6 +357,7 @@
</changeSet> </changeSet>
<changeSet author="athou" id="status-cleanup"> <changeSet author="athou" id="status-cleanup">
<validCheckSum>7:cf40ae235c2d4086c5fa6ac64102c6a9</validCheckSum>
<delete tableName="FEEDENTRYSTATUSES"> <delete tableName="FEEDENTRYSTATUSES">
<where>read_status = false and starred = false</where> <where>read_status = false and starred = false</where>
</delete> </delete>

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.0.xsd">
<changeSet author="athou" id="scroll-speed">
<addColumn tableName="USERSETTINGS">
<column name="scroll_speed" type="BIGINT" />
</addColumn>
</changeSet>
<changeSet author="athou" id="set-default-scroll-speed">
<update tableName="USERSETTINGS">
<column name="scroll_speed" valueNumeric="400" />
</update>
</changeSet>
<changeSet author="athou" id="create-tags-table">
<validCheckSum>7:fdd37bdee09c8fbbcbcd867b05decaae</validCheckSum>
<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_name_index">
<column name="user_id" />
<column name="entry_id" />
<column name="name" />
</createIndex>
</changeSet>
<changeSet author="athou" id="add-full-refresh-timestamp">
<addColumn tableName="USERS">
<column name="last_full_refresh" type="DATETIME" />
</addColumn>
</changeSet>
</databaseChangeLog>

View File

@@ -7,5 +7,6 @@
<include file="changelogs/db.changelog-1.1.xml" /> <include file="changelogs/db.changelog-1.1.xml" />
<include file="changelogs/db.changelog-1.2.xml" /> <include file="changelogs/db.changelog-1.2.xml" />
<include file="changelogs/db.changelog-1.3.xml" /> <include file="changelogs/db.changelog-1.3.xml" />
<include file="changelogs/db.changelog-1.4.xml" />
</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=استورد
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=الترتيب حسب التاريخ تصاعدي / ت
toolbar.titles_only=العناوين فقط toolbar.titles_only=العناوين فقط
toolbar.expanded_view=عرض موسع toolbar.expanded_view=عرض موسع
toolbar.mark_all_as_read=اعتبر الكل مقروء toolbar.mark_all_as_read=اعتبر الكل مقروء
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=العناصر الأقدم من يوم toolbar.mark_all_older_day=العناصر الأقدم من يوم
toolbar.mark_all_older_week=العناصر الأقدم من أسبوع toolbar.mark_all_older_week=العناصر الأقدم من أسبوع
toolbar.mark_all_older_two_weeks=العناصر الأقدم من أسبوعين toolbar.mark_all_older_two_weeks=العناصر الأقدم من أسبوعين
@@ -66,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
@@ -83,7 +87,10 @@ details.queued_for_refresh=Queued for refresh
details.feed_url=Feed URL 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.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
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
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Generate new API key
profile.generate_new_api_key_info=Changing password will generate a new API key profile.generate_new_api_key_info=Changing password will generate a new API key
profile.opml_export=OPML export profile.opml_export=OPML export
profile.delete_account=Delete account profile.delete_account=Delete account
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=REST API about.rest_api=REST API
about.keyboard_shortcuts=Keyboard shortcuts about.keyboard_shortcuts=Keyboard shortcuts

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
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc = Seřadit podle nejnovějšího/nejstaršího
toolbar.titles_only = Zobrazit jenom titulky toolbar.titles_only = Zobrazit jenom titulky
toolbar.expanded_view = Rozšířený náhled toolbar.expanded_view = Rozšířený náhled
toolbar.mark_all_as_read = Označit vše jako přečtené toolbar.mark_all_as_read = Označit vše jako přečtené
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day = Položky starší než den toolbar.mark_all_older_day = Položky starší než den
toolbar.mark_all_older_week = Položky starší než týden toolbar.mark_all_older_week = Položky starší než týden
toolbar.mark_all_older_two_weeks = Položky starší než dva týdny toolbar.mark_all_older_two_weeks = Položky starší než dva týdny
@@ -66,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)
@@ -83,7 +87,10 @@ details.queued_for_refresh = Ve frontě na obnovu
details.feed_url = URL RSS zdroje 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.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
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
@@ -98,6 +105,7 @@ profile.generate_new_api_key = Vygenerovat nový API klíč
profile.generate_new_api_key_info = Změnou hesla vygenerujete nový API klíč profile.generate_new_api_key_info = Změnou hesla vygenerujete nový API klíč
profile.opml_export = exportovat do formátu OPML profile.opml_export = exportovat do formátu OPML
profile.delete_account = Odstranit účet profile.delete_account = Odstranit účet
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api = REST API about.rest_api = REST API
about.keyboard_shortcuts = Klávesové zkratky about.keyboard_shortcuts = Klávesové zkratky

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
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Trefnu yn ôl dyddiad
toolbar.titles_only=Teitlau yn unig toolbar.titles_only=Teitlau yn unig
toolbar.expanded_view=Golwg estynedig toolbar.expanded_view=Golwg estynedig
toolbar.mark_all_as_read=Nodi'r cyfan fel wedi ei ddarllen toolbar.mark_all_as_read=Nodi'r cyfan fel wedi ei ddarllen
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=Eitemau hyn na diwrnod toolbar.mark_all_older_day=Eitemau hyn na diwrnod
toolbar.mark_all_older_week=Eitemau hyn nag wythnos toolbar.mark_all_older_week=Eitemau hyn nag wythnos
toolbar.mark_all_older_two_weeks=Eitemau hyn na phythefnos toolbar.mark_all_older_two_weeks=Eitemau hyn na phythefnos
@@ -66,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
@@ -83,7 +87,10 @@ details.queued_for_refresh=Ciwiwyd i'w adnewyddu
details.feed_url=URL Ffrwd 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.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
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
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Creu allwedd API newydd
profile.generate_new_api_key_info=Mae newid cyfrinair yn creu allwedd API newydd profile.generate_new_api_key_info=Mae newid cyfrinair yn creu allwedd API newydd
profile.opml_export=Allforio OPML profile.opml_export=Allforio OPML
profile.delete_account=Dileu cyfrif profile.delete_account=Dileu cyfrif
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=REST API about.rest_api=REST API
about.keyboard_shortcuts=Llwybr byr bysellfwrdd about.keyboard_shortcuts=Llwybr byr bysellfwrdd

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
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Sorter efter dato ny/gammel
toolbar.titles_only=Kun titler toolbar.titles_only=Kun titler
toolbar.expanded_view=Udvidet visning toolbar.expanded_view=Udvidet visning
toolbar.mark_all_as_read=Marker alle som læst toolbar.mark_all_as_read=Marker alle som læst
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=Artikler ældere end én dag toolbar.mark_all_older_day=Artikler ældere end én dag
toolbar.mark_all_older_week=Artikler ældere end én uge toolbar.mark_all_older_week=Artikler ældere end én uge
toolbar.mark_all_older_two_weeks=Artikler ældere end to uger toolbar.mark_all_older_two_weeks=Artikler ældere end to uger
@@ -66,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
@@ -83,7 +87,10 @@ details.queued_for_refresh=I kø til opdatering
details.feed_url=URL for abonnement 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.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
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
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Generer ny API nøgle
profile.generate_new_api_key_info=Ændring af adgangskode vil generere en ny API nøgle profile.generate_new_api_key_info=Ændring af adgangskode vil generere en ny API nøgle
profile.opml_export=OPML eksport profile.opml_export=OPML eksport
profile.delete_account=Slet konto profile.delete_account=Slet konto
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=REST API about.rest_api=REST API
about.keyboard_shortcuts=Tastaturgenveje about.keyboard_shortcuts=Tastaturgenveje

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
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Nach Datum sortieren (auf-/absteigend)
toolbar.titles_only=Nur Überschriften toolbar.titles_only=Nur Überschriften
toolbar.expanded_view=Ausgedehnte Ansicht toolbar.expanded_view=Ausgedehnte Ansicht
toolbar.mark_all_as_read=Alle Artikel als gelesen markieren toolbar.mark_all_as_read=Alle Artikel als gelesen markieren
toolbar.mark_all_older_12_hours=Artikel älter als 12 Stunden
toolbar.mark_all_older_day=Artikel älter als ein Tag toolbar.mark_all_older_day=Artikel älter als ein Tag
toolbar.mark_all_older_week=Artikel älter als eine Woche toolbar.mark_all_older_week=Artikel älter als eine Woche
toolbar.mark_all_older_two_weeks=Artikel älter als zwei Wochen toolbar.mark_all_older_two_weeks=Artikel älter als zwei Wochen
@@ -66,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
@@ -77,13 +81,16 @@ details.name=Name
details.category=Kategorie details.category=Kategorie
details.position=Position details.position=Position
details.last_refresh=Letzte Aktualisierung details.last_refresh=Letzte Aktualisierung
details.message=Last refresh message ####### Needs translation details.message=Nachricht der letzten Aktualisierung
details.next_refresh=Nächste Aktualisierung details.next_refresh=Nächste Aktualisierung
details.queued_for_refresh=Wartet auf Aktualisierung details.queued_for_refresh=Wartet auf Aktualisierung
details.feed_url=Feed Adresse 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.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
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
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Generiere einen neuen API key
profile.generate_new_api_key_info=Das Ändern des Passwortes erzeugt einen neuen API Schlüssel profile.generate_new_api_key_info=Das Ändern des Passwortes erzeugt einen neuen API Schlüssel
profile.opml_export=OPML exportieren profile.opml_export=OPML exportieren
profile.delete_account=Lösche den Account profile.delete_account=Lösche den Account
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=REST API about.rest_api=REST API
about.keyboard_shortcuts=Tastatur Kurzbefehle about.keyboard_shortcuts=Tastatur Kurzbefehle

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
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Sort by date asc/desc
toolbar.titles_only=Titles only toolbar.titles_only=Titles only
toolbar.expanded_view=Expanded view toolbar.expanded_view=Expanded view
toolbar.mark_all_as_read=Mark all as read toolbar.mark_all_as_read=Mark all as read
toolbar.mark_all_older_12_hours=Items older than 12 hours
toolbar.mark_all_older_day=Items older than a day toolbar.mark_all_older_day=Items older than a day
toolbar.mark_all_older_week=Items older than a week toolbar.mark_all_older_week=Items older than a week
toolbar.mark_all_older_two_weeks=Items older than two weeks toolbar.mark_all_older_two_weeks=Items older than two weeks
@@ -66,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)
settings.scroll_speed.help=set to 0 to disable
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
@@ -83,7 +87,10 @@ details.queued_for_refresh=Queued for refresh
details.feed_url=Feed URL 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.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed?
details.delete_category_confirmation=Are you sure you want to delete this category?
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
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Generate new API key
profile.generate_new_api_key_info=Changing password will generate a new API key profile.generate_new_api_key_info=Changing password will generate a new API key
profile.opml_export=OPML export profile.opml_export=OPML export
profile.delete_account=Delete account profile.delete_account=Delete account
profile.delete_account_confirmation=Delete your account? There's no turning back!
about.rest_api=REST API about.rest_api=REST API
about.keyboard_shortcuts=Keyboard shortcuts about.keyboard_shortcuts=Keyboard shortcuts
@@ -145,4 +153,4 @@ about.shortcuts.fullscreen=toggle full screen mode
about.shortcuts.font_size=increase/decrease font size of the current entry about.shortcuts.font_size=increase/decrease font size of the current entry
about.shortcuts.go_to_all=go to the All view about.shortcuts.go_to_all=go to the All view
about.shortcuts.go_to_starred=go to the Starred view about.shortcuts.go_to_starred=go to the Starred view
about.shortcuts.feed_search=navigate to a subscription by entering the subscription name about.shortcuts.feed_search=navigate to a subscription by entering the subscription 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
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Ordenar por fecha asc/desc
toolbar.titles_only=Sólo Títulos toolbar.titles_only=Sólo Títulos
toolbar.expanded_view=Vista Expandida toolbar.expanded_view=Vista Expandida
toolbar.mark_all_as_read=Marcar todos como leído toolbar.mark_all_as_read=Marcar todos como leído
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=Artículos anteriores a un día toolbar.mark_all_older_day=Artículos anteriores a un día
toolbar.mark_all_older_week=Artículos más de una semana toolbar.mark_all_older_week=Artículos más de una semana
toolbar.mark_all_older_two_weeks=Artículos más de does semanas toolbar.mark_all_older_two_weeks=Artículos más de does semanas
@@ -66,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
@@ -83,7 +87,10 @@ details.queued_for_refresh=Queued for refresh ####### Needs translation
details.feed_url=URL del Canal 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.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
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
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Generar nueva llave API
profile.generate_new_api_key_info=Al cambiar la contraseña se generará una nueva llave API profile.generate_new_api_key_info=Al cambiar la contraseña se generará una nueva llave API
profile.opml_export=Exportación de OPML profile.opml_export=Exportación de OPML
profile.delete_account=Eliminar cuenta profile.delete_account=Eliminar cuenta
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=REST API about.rest_api=REST API
about.keyboard_shortcuts=Atajos de teclado about.keyboard_shortcuts=Atajos de teclado

View File

@@ -6,6 +6,7 @@ global.download=بارگیری
global.link=پیوند global.link=پیوند
global.bookmark=بوکمارک global.bookmark=بوکمارک
global.close=بستن global.close=بستن
global.tags=برجسپ‌ها
tree.subscribe=مشترک شوید tree.subscribe=مشترک شوید
tree.import=درون‌ریزی tree.import=درون‌ریزی
@@ -31,11 +32,12 @@ toolbar.all=همه
toolbar.previous_entry=مطلب قبلی toolbar.previous_entry=مطلب قبلی
toolbar.next_entry=مطلب بعدی toolbar.next_entry=مطلب بعدی
toolbar.refresh=تازه‌سازی toolbar.refresh=تازه‌سازی
toolbar.refresh_all=Force refresh all my feeds ####### Needs translation toolbar.refresh_all=مجبورکردن تازه‌سازی همهٔ خوراک‌ها
toolbar.sort_by_asc_desc=مرتب‌کردن بر اساس تاریخ به‌صورت صعودی/نزولی toolbar.sort_by_asc_desc=مرتب‌کردن بر اساس تاریخ به‌صورت صعودی/نزولی
toolbar.titles_only=فقط عنوان‌ها toolbar.titles_only=فقط عنوان‌ها
toolbar.expanded_view=نمای گسترش‌یافته toolbar.expanded_view=نمای گسترش‌یافته
toolbar.mark_all_as_read=علامت‌گذاری تمامی مطالب به‌عنوان خوانده‌شده toolbar.mark_all_as_read=علامت‌گذاری تمامی مطالب به‌عنوان خوانده‌شده
toolbar.mark_all_older_12_hours=مطالب قدیمی‌تر از ۱۲ ساعت
toolbar.mark_all_older_day=مطالب قدیمی‌تر از یک روز toolbar.mark_all_older_day=مطالب قدیمی‌تر از یک روز
toolbar.mark_all_older_week=مطالب قدیمی‌تر از یک هفته toolbar.mark_all_older_week=مطالب قدیمی‌تر از یک هفته
toolbar.mark_all_older_two_weeks=مطالب قدیمی تر از چند هفته قیل toolbar.mark_all_older_two_weeks=مطالب قدیمی تر از چند هفته قیل
@@ -52,8 +54,8 @@ view.error_while_loading_feed=متأسفانه، هنگام بارگیری ای
view.keep_unread=خوانده‌نشده نگه‌دار view.keep_unread=خوانده‌نشده نگه‌دار
view.no_unread_items=هیچ مطلب خوانده‌نشده‌ای ندارد. view.no_unread_items=هیچ مطلب خوانده‌نشده‌ای ندارد.
view.mark_up_to_here=تا اینجا را خوانده‌شده در نظر بگیر view.mark_up_to_here=تا اینجا را خوانده‌شده در نظر بگیر
view.search_for=searching for: ####### Needs translation view.search_for=جستجو برای:
view.no_search_results=No match found for the requested keywords ####### Needs translation view.no_search_results=هیج نتیجه‌ای برای کلیدواژه‌های درخواستی یافت نشد
feedsearch.hint=نوشتن بر روی یک اشتراک... feedsearch.hint=نوشتن بر روی یک اشتراک...
feedsearch.help=دکمهٔ بازگشت برای انتخاب و دکمه‌های جهت‌دار را برای ناوبری استفاده کن. feedsearch.help=دکمهٔ بازگشت برای انتخاب و دکمه‌های جهت‌دار را برای ناوبری استفاده کن.
@@ -66,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=سرعت لغزش هنگام گشتن بین مدخل‌ها (به میلی‌ثانیه)
settings.scroll_speed.help=قراردادن به ۰ برای غیرفعال‌کردن
settings.theme=پوسته settings.theme=پوسته
settings.submit_your_theme=پوستهٔ خود را ارسال‌کنید settings.submit_your_theme=پوستهٔ خود را ارسال‌کنید
settings.custom_css=سی‌اس‌اس شخصی‌سازی‌شده settings.custom_css=سی‌اس‌اس شخصی‌سازی‌شده
@@ -77,13 +81,16 @@ details.name=نام
details.category=دسته details.category=دسته
details.position=موقعیت details.position=موقعیت
details.last_refresh=آخرین بروزرسانی details.last_refresh=آخرین بروزرسانی
details.message=Last refresh message ####### Needs translation details.message=پیام آخرین تازه‌سازی
details.next_refresh=بروزرسانی بعدی details.next_refresh=بروزرسانی بعدی
details.queued_for_refresh=منتظر برای بروزرسانی details.queued_for_refresh=منتظر برای بروزرسانی
details.feed_url=نشانی خوراک details.feed_url=نشانی خوراک
details.generate_api_key_first=ابتدا یک کلید API در نمایهٔ خود ایجاد کنید. details.generate_api_key_first=ابتدا یک کلید API در نمایهٔ خود ایجاد کنید.
details.unsubscribe=لغو اشتراک details.unsubscribe=لغو اشتراک
details.unsubscribe_confirmation=مطمئنید می‌خواهید از این این لغو اشتراک کنید؟
details.delete_category_confirmation=مطمئنید می‌خواهید این رده را حذف کنید؟
details.category_details=جزئیات دسته details.category_details=جزئیات دسته
details.tag_details=جزئیات برچسپ
details.parent_category=ردهٔ پدر details.parent_category=ردهٔ پدر
profile.user_name=نام کاربری profile.user_name=نام کاربری
@@ -98,10 +105,11 @@ profile.generate_new_api_key=ایجاد کلید جدید API
profile.generate_new_api_key_info=تغییر گذرواژه کلید API به‌وجود خواهد آورد. profile.generate_new_api_key_info=تغییر گذرواژه کلید API به‌وجود خواهد آورد.
profile.opml_export=خارج‌سازی OPML profile.opml_export=خارج‌سازی OPML
profile.delete_account=حذف حساب کاربری profile.delete_account=حذف حساب کاربری
profile.delete_account_confirmation=حذف حسابتان؟ بازگشتی وجود ندارد!
about.rest_api=REST API about.rest_api=REST API
about.keyboard_shortcuts=کلیدهای میانبر about.keyboard_shortcuts=کلیدهای میانبر
about.version=CommaFeed version ####### Needs translation about.version=نسخهٔ کامافید
about.line1_prefix=کامافید یک پروژه متن‌باز است. مخازن آن در about.line1_prefix=کامافید یک پروژه متن‌باز است. مخازن آن در
about.line1_suffix=میزبانی می‌شود. about.line1_suffix=میزبانی می‌شود.
about.line2_prefix=اگر شما به مسئله‌ای برخورده اید، لطفاً آن را در صفحه مسائل گزارش دهید about.line2_prefix=اگر شما به مسئله‌ای برخورده اید، لطفاً آن را در صفحه مسائل گزارش دهید
@@ -143,7 +151,7 @@ about.shortcuts.mark_all_as_read=علامت‌گذاری تمامی مطالب
about.shortcuts.open_in_new_tab_mark_as_read=باز‌کردن مطلب در سربرگ جدید و علامت‌گذاری آن به‌عنوان خوانده‌شده about.shortcuts.open_in_new_tab_mark_as_read=باز‌کردن مطلب در سربرگ جدید و علامت‌گذاری آن به‌عنوان خوانده‌شده
about.shortcuts.fullscreen=فعال/غیرفعال‌کردن حالت تمام صفحه about.shortcuts.fullscreen=فعال/غیرفعال‌کردن حالت تمام صفحه
about.shortcuts.font_size=افزایش/کاهش اندازهٔ قلم مدخل فعلی about.shortcuts.font_size=افزایش/کاهش اندازهٔ قلم مدخل فعلی
about.shortcuts.go_to_all=go to the All view ####### Needs translation about.shortcuts.go_to_all=رفتن به نمای همه
about.shortcuts.go_to_starred=go to the Starred view ####### Needs translation about.shortcuts.go_to_starred=رفتن به نمای ستاره داده‌شده‌ها
about.shortcuts.feed_search=ناوبری به یک اشتراک با نوشتن نام اشتراک about.shortcuts.feed_search=ناوبری به یک اشتراک با نوشتن نام اشتراک

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=Tagit
tree.subscribe=Tilaa syöte tree.subscribe=Tilaa syöte
tree.import=Tuo tree.import=Tuo
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Järjestä päivämäärän mukaan nousevasti/laskevast
toolbar.titles_only=Näytä vain otsikot toolbar.titles_only=Näytä vain otsikot
toolbar.expanded_view=Laajennettu näkymä toolbar.expanded_view=Laajennettu näkymä
toolbar.mark_all_as_read=Merkitse kaikki luetuiksi toolbar.mark_all_as_read=Merkitse kaikki luetuiksi
toolbar.mark_all_older_12_hours=12 tuntia vanhemmat otsikot
toolbar.mark_all_older_day=Päivää vanhemmat otsikot toolbar.mark_all_older_day=Päivää vanhemmat otsikot
toolbar.mark_all_older_week=Viikkoa vanhemmat otsikot toolbar.mark_all_older_week=Viikkoa vanhemmat otsikot
toolbar.mark_all_older_two_weeks=Kahta viikkoa vanhemmat otsikot toolbar.mark_all_older_two_weeks=Kahta viikkoa vanhemmat otsikot
@@ -52,8 +54,8 @@ view.error_while_loading_feed=Virhe tilausta ladattaessa
view.keep_unread=Pidä lukemattomana view.keep_unread=Pidä lukemattomana
view.no_unread_items=ei sisällä lukemattomia otsikoita. view.no_unread_items=ei sisällä lukemattomia otsikoita.
view.mark_up_to_here=Merkitse luetuksi tähän asti view.mark_up_to_here=Merkitse luetuksi tähän asti
view.search_for=searching for: ####### Needs translation view.search_for=Etsi sanoilla:
view.no_search_results=No match found for the requested keywords ####### Needs translation view.no_search_results=Ei tuloksia annetuilla hakusanoilla.
feedsearch.hint=Kirjoita syötteen nimi... feedsearch.hint=Kirjoita syötteen nimi...
feedsearch.help=Siirry syötteiden välillä nuolinäppäimillä ja valitse syöte enterillä. feedsearch.help=Siirry syötteiden välillä nuolinäppäimillä ja valitse syöte enterillä.
@@ -66,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=Vieritysnopeus otsikoiden välillä navigoidessa (millisekunneissa)
settings.scroll_speed.help=Aseta 0 poistaaksesi vieritys käytöstä.
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
@@ -77,13 +81,16 @@ details.name=Nimi
details.category=Kansio details.category=Kansio
details.position=Paikka details.position=Paikka
details.last_refresh=Viimeisin päivitys details.last_refresh=Viimeisin päivitys
details.message=Last refresh message ####### Needs translation details.message=Viimeisimmän päivityksen viesti
details.next_refresh=Seuraava päivitys details.next_refresh=Seuraava päivitys
details.queued_for_refresh=Jonossa päivitettäväksi details.queued_for_refresh=Jonossa päivitettäväksi
details.feed_url=Syötteen osoite 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.unsubscribe_confirmation=Haluatko varmasti lopettaa tämän syötteen tilauksen?
details.delete_category_confirmation=Haluatko varmasti poistaa tämän kansion?
details.category_details=Kansion tiedot details.category_details=Kansion tiedot
details.tag_details=Tagin tiedot
details.parent_category=Yläkansio details.parent_category=Yläkansio
profile.user_name=Käyttäjänimi profile.user_name=Käyttäjänimi
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Luo uusi API-avain
profile.generate_new_api_key_info=Salasanan vaihtaminen luo uuden API-avaimen profile.generate_new_api_key_info=Salasanan vaihtaminen luo uuden API-avaimen
profile.opml_export=OPML vienti profile.opml_export=OPML vienti
profile.delete_account=Poista tunnus profile.delete_account=Poista tunnus
profile.delete_account_confirmation=Haluatko varmasti poistaa tunnuksesi? Tätä ei voi perua!
about.rest_api=REST-API about.rest_api=REST-API
about.keyboard_shortcuts=Näppäinoikotiet about.keyboard_shortcuts=Näppäinoikotiet

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
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Trier par date croissante/décroissante
toolbar.titles_only=Titres uniquement toolbar.titles_only=Titres uniquement
toolbar.expanded_view=Vue étendue toolbar.expanded_view=Vue étendue
toolbar.mark_all_as_read=Tout marquer comme lu toolbar.mark_all_as_read=Tout marquer comme lu
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=Articles de plus d'un jour toolbar.mark_all_older_day=Articles de plus d'un jour
toolbar.mark_all_older_week=Articles de plus d'une semaine toolbar.mark_all_older_week=Articles de plus d'une semaine
toolbar.mark_all_older_two_weeks=Articles de plus d'un mois toolbar.mark_all_older_two_weeks=Articles de plus d'un mois
@@ -66,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
@@ -83,7 +87,10 @@ details.queued_for_refresh=En file d'attente
details.feed_url=URL du flux 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.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
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
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Générer une nouvelle clé API
profile.generate_new_api_key_info=Changer de mot de passe va générer une nouvelle clé API profile.generate_new_api_key_info=Changer de mot de passe va générer une nouvelle clé API
profile.opml_export=Export du fichier OPML profile.opml_export=Export du fichier OPML
profile.delete_account=Effacer le compte profile.delete_account=Effacer le compte
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=API REST about.rest_api=API REST
about.keyboard_shortcuts=Raccourcis clavier about.keyboard_shortcuts=Raccourcis clavier

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
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Ordenar por data asc/desc
toolbar.titles_only=Só títulos toolbar.titles_only=Só títulos
toolbar.expanded_view=Vista expandida toolbar.expanded_view=Vista expandida
toolbar.mark_all_as_read=Marcar todos como lidos toolbar.mark_all_as_read=Marcar todos como lidos
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=Artigos anteriores a un día toolbar.mark_all_older_day=Artigos anteriores a un día
toolbar.mark_all_older_week=Artigos de máis de unha semana toolbar.mark_all_older_week=Artigos de máis de unha semana
toolbar.mark_all_older_two_weeks=Artigos de máis de dúas semanas toolbar.mark_all_older_two_weeks=Artigos de máis de dúas semanas
@@ -66,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
@@ -83,7 +87,10 @@ details.queued_for_refresh=En cola para actualizar
details.feed_url=URL da fonte 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.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
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
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Xerar nova chave da API
profile.generate_new_api_key_info=Ao cambiar o contrasinal xerarase unha nova chave API profile.generate_new_api_key_info=Ao cambiar o contrasinal xerarase unha nova chave API
profile.opml_export=Exportación de OPML profile.opml_export=Exportación de OPML
profile.delete_account=Eliminar conta profile.delete_account=Eliminar conta
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=REST API about.rest_api=REST API
about.keyboard_shortcuts=Atallos de teclado about.keyboard_shortcuts=Atallos de teclado

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=درینأدأن
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=تاریخˇ سر دچئن
toolbar.titles_only=خالی تیتران toolbar.titles_only=خالی تیتران
toolbar.expanded_view=واشاده نما toolbar.expanded_view=واشاده نما
toolbar.mark_all_as_read=همه‌ته مطالبه چاکون بخانده toolbar.mark_all_as_read=همه‌ته مطالبه چاکون بخانده
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=یک روز پیشترˇ مطالب toolbar.mark_all_older_day=یک روز پیشترˇ مطالب
toolbar.mark_all_older_week=یک هفته پیشترˇ مطالب toolbar.mark_all_older_week=یک هفته پیشترˇ مطالب
toolbar.mark_all_older_two_weeks=چن هفته پیشترˇ مطالب toolbar.mark_all_older_two_weeks=چن هفته پیشترˇ مطالب
@@ -66,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=سی‌اس‌اس شخصی‌سازی‌شده
@@ -83,7 +87,10 @@ details.queued_for_refresh=منتظر برای بروزرسانی
details.feed_url=نشانی خوراک details.feed_url=نشانی خوراک
details.generate_api_key_first=ابتدا یک کلید API در نمایهٔ خود ایجاد کنید. details.generate_api_key_first=ابتدا یک کلید API در نمایهٔ خود ایجاد کنید.
details.unsubscribe=لغو اشتراک details.unsubscribe=لغو اشتراک
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
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=کاربری نام
@@ -98,6 +105,7 @@ profile.generate_new_api_key=تازه کلید چاگودن API
profile.generate_new_api_key_info=رمزه عوضأگودن API کلیده چاکونه. profile.generate_new_api_key_info=رمزه عوضأگودن API کلیده چاکونه.
profile.opml_export=برینأدأن OPML profile.opml_export=برینأدأن OPML
profile.delete_account=کاربری حسابه پاکودن profile.delete_account=کاربری حسابه پاکودن
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=REST API about.rest_api=REST API
about.keyboard_shortcuts=وئر زئنˇ کلیدان about.keyboard_shortcuts=وئر زئنˇ کلیدان

View File

@@ -1,149 +1,157 @@
global.save=Mentés global.save=Mentés
global.cancel=Mégsem global.cancel=Mégsem
global.delete=Törlés global.delete=Törlés
global.required=Szükséges global.required=Szükséges
global.download=Letöltés 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=Címkék
tree.subscribe=Feliratkozás
tree.import=Importálás tree.subscribe=Feliratkozás
tree.new_category=Új kategória tree.import=Importálás
tree.all=Összes tree.new_category=Új kategória
tree.starred=Csillagozott tree.all=Összes
tree.starred=Csillagozott
subscribe.feed_url=Hírcsatorna URL
subscribe.feed_name=Hírcsatorna neve subscribe.feed_url=Hírcsatorna URL
subscribe.category=Kategória subscribe.feed_name=Hírcsatorna neve
subscribe.category=Kategória
import.google_reader_prefix=Engedd meg, hogy importáljuk a hírcsatornáidat a
import.google_reader_suffix= fiókjából. import.google_reader_prefix=Engedd meg, hogy importáljuk a hírcsatornáidat a
import.google_download=Alternatívaként, feltöltheti a subscriptions.xml fájlt. import.google_reader_suffix= fiókjából.
import.google_download_link=Letöltheti innen. import.google_download=Alternatívaként, feltöltheti a subscriptions.xml fájlt.
import.xml_file=OPML Fájl import.google_download_link=Letöltheti innen.
import.xml_file=OPML Fájl
new_category.name=Név
new_category.parent=Szülő new_category.name=Név
new_category.parent=Szülő
toolbar.unread=Olvasatlan
toolbar.all=Összes toolbar.unread=Olvasatlan
toolbar.previous_entry=Előző elem toolbar.all=Összes
toolbar.next_entry=Következő elem toolbar.previous_entry=Előző elem
toolbar.refresh=Frissítés toolbar.next_entry=Következő elem
toolbar.refresh_all=Force refresh all my feeds ####### Needs translation toolbar.refresh=Frissítés
toolbar.sort_by_asc_desc=Rendezés időrend szerint toolbar.refresh_all=Force refresh all my feeds ####### Needs translation
toolbar.titles_only=Csak cím toolbar.sort_by_asc_desc=Rendezés időrend szerint
toolbar.expanded_view=Részletes nézet toolbar.titles_only=Csak cím
toolbar.mark_all_as_read=Az összes megjelölése olvasottként toolbar.expanded_view=Részletes nézet
toolbar.mark_all_older_day=Régebbiek, mint egy nap toolbar.mark_all_as_read=Az összes megjelölése olvasottként
toolbar.mark_all_older_week=Régebbiek, mint egy hét toolbar.mark_all_older_12_hours=Régebbiek 12 óránál
toolbar.mark_all_older_two_weeks=Régebbiek, mint két hét toolbar.mark_all_older_day=Régebbiek, mint egy nap
toolbar.settings=Beállítások toolbar.mark_all_older_week=Régebbiek, mint egy hét
toolbar.profile=Profil toolbar.mark_all_older_two_weeks=Régebbiek, mint két hét
toolbar.admin=Admin toolbar.settings=Beállítások
toolbar.about=Névjegy toolbar.profile=Profil
toolbar.logout=Kilépés toolbar.admin=Admin
toolbar.donate=Anyagi támogatás toolbar.about=Névjegy
toolbar.logout=Kilépés
view.entry_source=from ####### Needs translation toolbar.donate=Anyagi támogatás
view.entry_author=by ####### Needs translation
view.error_while_loading_feed=Hiba történt ennek a hírcsatornának a betöltésekor view.entry_source=forrás
view.keep_unread=Megtartása olvasatlanként view.entry_author=szerző
view.no_unread_items=nincsen olvasatlan eleme. view.error_while_loading_feed=Hiba történt ennek a hírcsatornának a betöltésekor
view.mark_up_to_here=Mark as read up to here ####### Needs translation view.keep_unread=Megtartása olvasatlanként
view.search_for=searching for: ####### Needs translation view.no_unread_items=nincsen olvasatlan eleme.
view.no_search_results=No match found for the requested keywords ####### Needs translation view.mark_up_to_here=Megjelölés olvasottnak eddig
view.search_for=keresés erre:
feedsearch.hint=Keressen a hírcsatornák között... view.no_search_results=Nem található semmi erre a keresett szóra
feedsearch.help=Használja a nyíl billentyűket a navigáláshoz, az enter-t a kiválasztáshoz.
feedsearch.result_prefix=Az ön feliratkozásai: feedsearch.hint=Keressen a hírcsatornák között...
feedsearch.help=Használja a nyíl billentyűket a navigáláshoz, az enter-t a kiválasztáshoz.
settings.general=Általános feedsearch.result_prefix=Az ön feliratkozásai:
settings.general.language=Nyelv
settings.general.language.contribute=Segítsen a fordításban settings.general=Általános
settings.general.show_unread=Mutassa azokat a hírcsatornákat és kategóriákat amelyekben nincsen olvasatlan bejegyzés settings.general.language=Nyelv
settings.general.social_buttons=Mutassa a közösségi oldalak megosztás gombjait settings.general.language.contribute=Segítsen a fordításban
settings.general.scroll_marks=Kiterjesztett nézetben, görgetéssel olvasottként jelöli meg a bejegyzést settings.general.show_unread=Mutassa azokat a hírcsatornákat és kategóriákat amelyekben nincsen olvasatlan bejegyzés
settings.appearance=Megjelenés settings.general.social_buttons=Mutassa a közösségi oldalak megosztás gombjait
settings.theme=Téma settings.general.scroll_marks=Kiterjesztett nézetben, görgetéssel olvasottként jelöli meg a bejegyzést
settings.submit_your_theme=Küldje el a témáját settings.appearance=Megjelenés
settings.custom_css=Saját CSS settings.scroll_speed=A görgetés sebessége, amikor a cikkek között navigál (miliszekundumban)
settings.scroll_speed.help=Írjon be 0-át a letiltáshoz
details.feed_details=Hírcsatorna részletei settings.theme=Téma
details.url=URL settings.submit_your_theme=Küldje el a témáját
details.website=Website ####### Needs translation settings.custom_css=Saját CSS
details.name=Név
details.category=Kategória details.feed_details=Hírcsatorna részletei
details.position=Position ####### Needs translation details.url=URL
details.last_refresh=Utolsó frissítés details.website=Weboldal
details.message=Last refresh message ####### Needs translation details.name=Név
details.next_refresh=Következő frissítés details.category=Kategória
details.queued_for_refresh=Frissítésre vár details.position=Pozició
details.feed_url=Hírcsatorna URL details.last_refresh=Utolsó frissítés
details.generate_api_key_first=A profiljában először egy API kulcsot kell generálnia. details.message=Utolsó frissítési üzenet
details.unsubscribe=Leiratkozás details.next_refresh=Következő frissítés
details.category_details=Kategória részletei details.queued_for_refresh=Frissítésre vár
details.parent_category=Szülő kategória details.feed_url=Hírcsatorna URL
details.generate_api_key_first=A profiljában először egy API kulcsot kell generálnia.
profile.user_name=Felhasználói név details.unsubscribe=Leiratkozás
profile.email=E-mail details.unsubscribe_confirmation=Biztos, hogy le akar iratkozni errről a csatornáról?
profile.change_password=Jelszó megváltoztatás details.delete_category_confirmation=Biztos, hog törölni akarja ezt a kategóriát?
profile.confirm_password=Jelszó megerősítése details.category_details=Kategória részletei
profile.minimum_6_chars=Legalább 8 karakter details.tag_details=Címke részletei
profile.passwords_do_not_match=A jelszavak nem egyeznek details.parent_category=Szülő kategória
profile.api_key=API kulcs
profile.api_key_not_generated=Még nincsen generálva profile.user_name=Felhasználói név
profile.generate_new_api_key=Új API kulcs generálása profile.email=E-mail
profile.generate_new_api_key_info=A jelszó megváltoztatása új API kulcsot generál profile.change_password=Jelszó megváltoztatás
profile.opml_export=OPML exportálása profile.confirm_password=Jelszó megerősítése
profile.delete_account=Fiók törlése profile.minimum_6_chars=Legalább 8 karakter
profile.passwords_do_not_match=A jelszavak nem egyeznek
about.rest_api=REST API profile.api_key=API kulcs
about.keyboard_shortcuts=Gyorsbillentyűk profile.api_key_not_generated=Még nincsen generálva
about.version=CommaFeed version ####### Needs translation profile.generate_new_api_key=Új API kulcs generálása
about.line1_prefix=A CommaFeed egy nyílt forrású projekt. A forrás megtalálható a profile.generate_new_api_key_info=A jelszó megváltoztatása új API kulcsot generál
about.line1_suffix=oldalán. profile.opml_export=OPML exportálása
about.line2_prefix=Ha hibába ütközik, kérjük jelentse azt a profile.delete_account=Fiók törlése
about.line2_suffix=projekt oldalán. profile.delete_account_confirmation=Törli a fiókját? Innen már nincs visszatérés!
about.line3=Ha tetszik önnek ez a szolgáltatás, akkor kérjük támogassa a fejlesztőket és, hogy fentarthassák a weboldalt.
about.line4=Akik jobban szeretnék az oldalt bitcon-nal támogatni, itt a cím about.rest_api=REST API
about.goodies=Hasznos dolgok about.keyboard_shortcuts=Gyorsbillentyűk
about.goodies.android_app=Android app ####### Needs translation about.version=CommaFeed verzió
about.goodies.subscribe_url=Feliratkozás az URL-re about.line1_prefix=A CommaFeed egy nyílt forrású projekt. A forrás megtalálható a
about.goodies.chrome_extension=Chrome bővítmény about.line1_suffix=oldalán.
about.goodies.firefox_extension=Firefox kiterjesztés about.line2_prefix=Ha hibába ütközik, kérjük jelentse azt a
about.goodies.opera_extension=Opera kiterjesztés about.line2_suffix=projekt oldalán.
about.goodies.subscribe_bookmarklet=Feliratkozás bookmarklet hozzáadása (klikkeléssel) about.line3=Ha tetszik önnek ez a szolgáltatás, akkor kérjük támogassa a fejlesztőket és, hogy fentarthassák a weboldalt.
about.goodies.subscribe_bookmarklet_asc=Oldest first ####### Needs translation about.line4=Akik jobban szeretnék az oldalt bitcon-nal támogatni, itt a cím
about.goodies.subscribe_bookmarklet_desc=Newest first ####### Needs translation about.goodies=Hasznos dolgok
about.goodies.next_unread_bookmarklet=Következő olvasatlan elem bookmarklet (húzza fel a könyvjelzősávba) about.goodies.android_app=Android alkalmazás
about.translation=Fordítás about.goodies.subscribe_url=Feliratkozás az URL-re
about.translation.message=Segítségét kérjük a CommaFeed fordításához. about.goodies.chrome_extension=Chrome bővítmény
about.translation.link=Nézze meg, hogyan tud segíteni ebben. about.goodies.firefox_extension=Firefox kiterjesztés
about.announcements=Bejelentések about.goodies.opera_extension=Opera kiterjesztés
about.rest_api.line1=A CommaFeed a JAX-RS-re és az AngularJS-re épül. Ezért a RESTA API elérhető. about.goodies.subscribe_bookmarklet=Feliratkozás bookmarklet hozzáadása (klikkeléssel)
about.rest_api.link_to_documentation=Link a dokumentációhoz. about.goodies.subscribe_bookmarklet_asc=Régebbiek először
about.goodies.subscribe_bookmarklet_desc=Újak először
about.shortcuts.mouse_middleclick=középső egérgomb about.goodies.next_unread_bookmarklet=Következő olvasatlan elem bookmarklet (húzza fel a könyvjelzősávba)
about.shortcuts.open_next_entry=következő hír megnyitása about.translation=Fordítás
about.shortcuts.open_previous_entry=előző hír megnyitása about.translation.message=Segítségét kérjük a CommaFeed fordításához.
about.shortcuts.spacebar=space/shift+space ####### Needs translation about.translation.link=Nézze meg, hogyan tud segíteni ebben.
about.shortcuts.move_page_down_up=moves the page down/up ####### Needs translation about.announcements=Bejelentések
about.shortcuts.focus_next_entry=megnyitás nélkül fókuszál a övetkező elemre about.rest_api.line1=A CommaFeed a JAX-RS-re és az AngularJS-re épül. Ezért a RESTA API elérhető.
about.shortcuts.focus_previous_entry=megnyitás nélkül fókuszál az előző elemre about.rest_api.link_to_documentation=Link a dokumentációhoz.
about.shortcuts.open_next_feed=a következő hírcsatorna vagy kategória megnyitása
about.shortcuts.open_previous_feed=az előző hírcsatorna vagy kategória megnyitása about.shortcuts.mouse_middleclick=középső egérgomb
about.shortcuts.open_close_current_entry=a jelenlegi elem megnyitása/bezárása about.shortcuts.open_next_entry=következő hír megnyitása
about.shortcuts.open_current_entry_in_new_window=a jelenlegi elem megnyitása új ablakban about.shortcuts.open_previous_entry=előző hír megnyitása
about.shortcuts.open_current_entry_in_new_window_background=a jelenlegi elem megnyitása a háttérben, új ablakban about.shortcuts.spacebar=szóköz/shift+szóköz
about.shortcuts.star_unstar=hírelem csillagozása about.shortcuts.move_page_down_up=fel/le lépkedhet az oldalon
about.shortcuts.mark_current_entry=elem megjelölése olvasottként about.shortcuts.focus_next_entry=megnyitás nélkül fókuszál a övetkező elemre
about.shortcuts.mark_all_as_read=az összes elem megjelölése olvasottként about.shortcuts.focus_previous_entry=megnyitás nélkül fókuszál az előző elemre
about.shortcuts.open_in_new_tab_mark_as_read=elem megnyitása új fülön és megjelölése olvasottként about.shortcuts.open_next_feed=a következő hírcsatorna vagy kategória megnyitása
about.shortcuts.fullscreen=toggle full screen mode ####### Needs translation about.shortcuts.open_previous_feed=az előző hírcsatorna vagy kategória megnyitása
about.shortcuts.font_size=increase/decrease font size of the current entry ####### Needs translation about.shortcuts.open_close_current_entry=a jelenlegi elem megnyitása/bezárása
about.shortcuts.go_to_all=go to the All view ####### Needs translation about.shortcuts.open_current_entry_in_new_window=a jelenlegi elem megnyitása új ablakban
about.shortcuts.go_to_starred=go to the Starred view ####### Needs translation about.shortcuts.open_current_entry_in_new_window_background=a jelenlegi elem megnyitása a háttérben, új ablakban
about.shortcuts.feed_search=név szerinti keresés a hírcsatornák között about.shortcuts.star_unstar=hírelem csillagozása
about.shortcuts.mark_current_entry=elem megjelölése olvasottként
about.shortcuts.mark_all_as_read=az összes elem megjelölése olvasottként
about.shortcuts.open_in_new_tab_mark_as_read=elem megnyitása új fülön és megjelölése olvasottként
about.shortcuts.fullscreen=teljes képernyős mód bekapcsolása
about.shortcuts.font_size=a jelenlegi elemnél növeli/csökkenti a betűméretet
about.shortcuts.go_to_all=átkapcsol az Összes nézetre
about.shortcuts.go_to_starred=átkapcsol a Csillagozott nézetre
about.shortcuts.feed_search=név szerinti keresés a hírcsatornák között

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
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Sort by date asc/desc
toolbar.titles_only=Solo titoli toolbar.titles_only=Solo titoli
toolbar.expanded_view=Espandi toolbar.expanded_view=Espandi
toolbar.mark_all_as_read=Contrassegna tutto come già letto toolbar.mark_all_as_read=Contrassegna tutto come già letto
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=Elementi più vecchi di un giorno toolbar.mark_all_older_day=Elementi più vecchi di un giorno
toolbar.mark_all_older_week=Elementi più vecchi di una settimana toolbar.mark_all_older_week=Elementi più vecchi di una settimana
toolbar.mark_all_older_two_weeks=Elementi più vecchi di due settimane toolbar.mark_all_older_two_weeks=Elementi più vecchi di due settimane
@@ -66,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
@@ -83,7 +87,10 @@ details.queued_for_refresh=In attesa per l'aggiornamento
details.feed_url=Feed URL 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.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
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
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Genera una nuova chiave API
profile.generate_new_api_key_info=Cambiando la password sarà generata una nuova chiave API profile.generate_new_api_key_info=Cambiando la password sarà generata una nuova chiave API
profile.opml_export=Esporta OPML profile.opml_export=Esporta OPML
profile.delete_account=Elimina account profile.delete_account=Elimina account
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=REST API about.rest_api=REST API
about.keyboard_shortcuts=Scorciatoie da tastiera about.keyboard_shortcuts=Scorciatoie da tastiera

View File

@@ -0,0 +1,157 @@
global.save=保存
global.cancel=取り消し
global.delete=削除
global.required=Required
global.download=ダウンロード
global.link=リンク
global.bookmark=ブックマーク
global.close=閉じる
global.tags=タグ
tree.subscribe=購読
tree.import=インポート
tree.new_category=新しいカテゴリー
tree.all=全て
tree.starred=スター付
subscribe.feed_url=フィードURL
subscribe.feed_name=フィード名
subscribe.category=カテゴリー
import.google_reader_prefix=Googleアカウントからフィードを
import.google_reader_suffix=インポートします。
import.google_download=または、お持ちのsubscriptions.xmlファイルをアップロードします。
import.google_download_link=このリンクからダウンロードして下さい。
import.xml_file=OPMLファイル
new_category.name=名前
new_category.parent=親カテゴリー
toolbar.unread=未読
toolbar.all=全て
toolbar.previous_entry=前のエントリー
toolbar.next_entry=次のエントリー
toolbar.refresh=更新
toolbar.refresh_all=全てのフィードを更新
toolbar.sort_by_asc_desc=昇順/降順にソート
toolbar.titles_only=タイトルのみ
toolbar.expanded_view=拡張ビュー
toolbar.mark_all_as_read=全て既読にする
toolbar.mark_all_older_12_hours=12時間以上前のアイテム
toolbar.mark_all_older_day=前日より前のアイテム
toolbar.mark_all_older_week=1週間以上前のアイテム
toolbar.mark_all_older_two_weeks=2週間以上前のアイテム
toolbar.settings=設定
toolbar.profile=Profile
toolbar.admin=管理者
toolbar.about=About
toolbar.logout=ログアウト
toolbar.donate=寄付
view.entry_source= より
view.entry_author= 著者
view.error_while_loading_feed=フィード読み込み中にエラーが発生しました。
view.keep_unread=未読として保持
view.no_unread_items=未読アイテムはありません。
view.mark_up_to_here=ここまで既読
view.search_for=検索:
view.no_search_results=検索結果はありません。
feedsearch.hint=購読フィードを入力...
feedsearch.help=Enterキーで選択、矢印キーで移動します。
feedsearch.result_prefix=見つかった購読フィード:
settings.general=一般
settings.general.language=言語
settings.general.language.contribute=翻訳に貢献する
settings.general.show_unread=未読エントリーのないフィードとカテゴリーを表示
settings.general.social_buttons=共有ボタンを表示
settings.general.scroll_marks=拡張ビューではエントリーのスクロールで既読にする
settings.appearance=外観
settings.scroll_speed=エントリー間のスクロールスピード(ミリ秒)
settings.scroll_speed.help=0に設定すると無効になります
settings.theme=テーマ
settings.submit_your_theme=テーマを登録する
settings.custom_css=カスタムCSS
details.feed_details=フィードの詳細
details.url=URL
details.website=Webサイト
details.name=名前
details.category=カテゴリー
details.position=位置
details.last_refresh=最終更新
details.message=最終更新メッセージ
details.next_refresh=次回更新
details.queued_for_refresh=更新キュー
details.feed_url=フィードURL
details.generate_api_key_first=最初にあなたのAPIキーを生成して下さい。
details.unsubscribe=購読解除
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
details.category_details=カテゴリー詳細
details.tag_details=タグ詳細
details.parent_category=親カテゴリー
profile.user_name=ユーザ名
profile.email=E-mail
profile.change_password=パスワードの変更
profile.confirm_password=変更パスワードの確認
profile.minimum_6_chars=6文字以上
profile.passwords_do_not_match=パスワードが一致しません
profile.api_key=APIキー
profile.api_key_not_generated=APIキーが生成されていません
profile.generate_new_api_key=新しいAPIキーを生成
profile.generate_new_api_key_info=パスワードの変更は新しいAPIキーが生成されます
profile.opml_export=OPMLエクスポート
profile.delete_account=アカウント削除
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=REST API
about.keyboard_shortcuts=キーボードショートカット
about.version=CommaFeedバージョン
about.line1_prefix=CommaFeedはオープンソースプロジェクトです。ソースは
about.line1_suffix=にホスティングされています。
about.line2_prefix=もし問題を登録したい場合、
about.line2_suffix=プロジェクトのissuesページに報告して下さい。
about.line3=このプロジェクトを気に入った場合、開発者やWebサイトの運営コストをサポートするための寄付を検討して下さいね。
about.line4=Bitcoinなら寄付できる方、アドレスはこちらです。
about.goodies=Goodies
about.goodies.android_app=Androidアプリ
about.goodies.subscribe_url=購読URL
about.goodies.chrome_extension=Chrome拡張
about.goodies.firefox_extension=Firefox拡張
about.goodies.opera_extension=Opera拡張
about.goodies.subscribe_bookmarklet=購読ブックマークレットを追加(クリック)
about.goodies.subscribe_bookmarklet_asc=古い順
about.goodies.subscribe_bookmarklet_desc=新しい順
about.goodies.next_unread_bookmarklet=次の未読アイテムブックマークレット(ブックマークバーへドラッグ)
about.translation=翻訳
about.translation.message=CommaFeedの翻訳に助けが必要です
about.translation.link=どうやって翻訳に貢献できるか見て下さい。
about.announcements=Announcements
about.rest_api.line1=CommaFeedはJAX-RSとAngularJSを使用しているので、REST APIも利用可能です。
about.rest_api.link_to_documentation=ドキュメントへのリンク
about.shortcuts.mouse_middleclick=中クリック
about.shortcuts.open_next_entry=次のエントリーを開く
about.shortcuts.open_previous_entry=前のエントリーを開く
about.shortcuts.spacebar=space/shift+space
about.shortcuts.move_page_down_up=ページ移動
about.shortcuts.focus_next_entry=次のエントリーを開かずにフォーカス移動
about.shortcuts.focus_previous_entry=前のエントリーを開かずにフォーカス移動
about.shortcuts.open_next_feed=次のフィード/カテゴリーを開く
about.shortcuts.open_previous_feed=前のフィード/カテゴリーを開く
about.shortcuts.open_close_current_entry=現在のエントリーを開く/閉じる
about.shortcuts.open_current_entry_in_new_window=現在のエントリーを新しいウィンドウで開く
about.shortcuts.open_current_entry_in_new_window_background=現在のエントリーを新しいバックグラウンドウィンドウで開く
about.shortcuts.star_unstar=現在のエントリーにスターを付ける/解除する
about.shortcuts.mark_current_entry=現在のエントリーを既読/未読にする
about.shortcuts.mark_all_as_read=全エントリーを既読にする
about.shortcuts.open_in_new_tab_mark_as_read=エントリーを既読にして新しいタブで開く
about.shortcuts.fullscreen=フルスクリーントグル
about.shortcuts.font_size=現在のエントリーのフォントサイズを大きく/小さくする
about.shortcuts.go_to_all=All viewに変更する
about.shortcuts.go_to_starred=スター付きviewに変更する
about.shortcuts.feed_search=購読画面(subscription 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=임포트
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Sort by date asc/desc ####### Needs translation
toolbar.titles_only=Titles only ####### Needs translation toolbar.titles_only=Titles only ####### Needs translation
toolbar.expanded_view=Expanded view ####### Needs translation toolbar.expanded_view=Expanded view ####### Needs translation
toolbar.mark_all_as_read=읽음표시 toolbar.mark_all_as_read=읽음표시
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=Items older than a day ####### Needs translation toolbar.mark_all_older_day=Items older than a day ####### Needs translation
toolbar.mark_all_older_week=Items older than a week ####### Needs translation toolbar.mark_all_older_week=Items older than a week ####### Needs translation
toolbar.mark_all_older_two_weeks=Items older than two weeks ####### Needs translation toolbar.mark_all_older_two_weeks=Items older than two weeks ####### Needs translation
@@ -66,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
@@ -83,7 +87,10 @@ details.queued_for_refresh=Queued for refresh ####### Needs translation
details.feed_url=피드 유알엘 details.feed_url=피드 유알엘
details.generate_api_key_first=당신의 프로필을 위해 API Key를 먼저 생성하세요. details.generate_api_key_first=당신의 프로필을 위해 API Key를 먼저 생성하세요.
details.unsubscribe=주소 삭제 details.unsubscribe=주소 삭제
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
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=사용자 이름
@@ -98,6 +105,7 @@ profile.generate_new_api_key=API Key 생성하기
profile.generate_new_api_key_info=비밀번호를 변경하면 새로운 API Key가 생성됩니다. profile.generate_new_api_key_info=비밀번호를 변경하면 새로운 API Key가 생성됩니다.
profile.opml_export=OPML export ####### Needs translation profile.opml_export=OPML export ####### Needs translation
profile.delete_account=프로필삭제 profile.delete_account=프로필삭제
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=REST API about.rest_api=REST API
about.keyboard_shortcuts=단축기 about.keyboard_shortcuts=단축기

View File

@@ -7,6 +7,7 @@ fr=Français
gl=Galician gl=Galician
glk=گیلکی glk=گیلکی
hu=Magyar hu=Magyar
ja=日本語
ko=한국어 ko=한국어
nl=Nederlands nl=Nederlands
nb=Norsk (bokmål) nb=Norsk (bokmål)

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
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Aturkan mengikut tarikh (baru/lama)
toolbar.titles_only=Tajuk sahaja toolbar.titles_only=Tajuk sahaja
toolbar.expanded_view=Wide view toolbar.expanded_view=Wide view
toolbar.mark_all_as_read=Tanda kesemuanya telah dibaca toolbar.mark_all_as_read=Tanda kesemuanya telah dibaca
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=Lebih lama daripada sehari toolbar.mark_all_older_day=Lebih lama daripada sehari
toolbar.mark_all_older_week=Lebih lama daripada seminggu toolbar.mark_all_older_week=Lebih lama daripada seminggu
toolbar.mark_all_older_two_weeks=Lebih lama daripada dua minggu toolbar.mark_all_older_two_weeks=Lebih lama daripada dua minggu
@@ -66,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
@@ -83,7 +87,10 @@ details.queued_for_refresh=Diaturkan untuk refresh
details.feed_url=URL Feed 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.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
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
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Jana API key baru
profile.generate_new_api_key_info=Pertukaran kata laluan akan menjanakan API key yang baru profile.generate_new_api_key_info=Pertukaran kata laluan akan menjanakan API key yang baru
profile.opml_export=Export OPML profile.opml_export=Export OPML
profile.delete_account=Padam akaun profile.delete_account=Padam akaun
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=REST API about.rest_api=REST API
about.keyboard_shortcuts=Pintasan papan kekunci about.keyboard_shortcuts=Pintasan papan kekunci

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
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Sorter etter dato ny/gammel
toolbar.titles_only=Kun titler toolbar.titles_only=Kun titler
toolbar.expanded_view=Utvidet visning toolbar.expanded_view=Utvidet visning
toolbar.mark_all_as_read=Merk alle som lest toolbar.mark_all_as_read=Merk alle som lest
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=Artikler eldre enn én dag toolbar.mark_all_older_day=Artikler eldre enn én dag
toolbar.mark_all_older_week=Artikler eldre enn én uke toolbar.mark_all_older_week=Artikler eldre enn én uke
toolbar.mark_all_older_two_weeks=Artikler eldre enn to uker toolbar.mark_all_older_two_weeks=Artikler eldre enn to uker
@@ -66,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
@@ -83,7 +87,10 @@ details.queued_for_refresh=I kø for oppdatering
details.feed_url=URL for abonnement 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.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
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
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Generer ny API-nøkkel
profile.generate_new_api_key_info=Endring av passord vil generere en ny API-nøkkel profile.generate_new_api_key_info=Endring av passord vil generere en ny API-nøkkel
profile.opml_export=OPML-eksport profile.opml_export=OPML-eksport
profile.delete_account=Slett bruker profile.delete_account=Slett bruker
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=REST API about.rest_api=REST API
about.keyboard_shortcuts=Hurtigtaster about.keyboard_shortcuts=Hurtigtaster

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
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Sorteer op datum opl/afl
toolbar.titles_only=Alleen titels toolbar.titles_only=Alleen titels
toolbar.expanded_view=Uitgebreide weergave toolbar.expanded_view=Uitgebreide weergave
toolbar.mark_all_as_read=Markeer alles als gelezen toolbar.mark_all_as_read=Markeer alles als gelezen
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=Artikelen ouder dan een dag toolbar.mark_all_older_day=Artikelen ouder dan een dag
toolbar.mark_all_older_week=Artikelen ouder dan een week toolbar.mark_all_older_week=Artikelen ouder dan een week
toolbar.mark_all_older_two_weeks=Artikelen ouder dan twee weken toolbar.mark_all_older_two_weeks=Artikelen ouder dan twee weken
@@ -66,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
@@ -83,7 +87,10 @@ details.queued_for_refresh=In wachtrij voor vernieuwing
details.feed_url=Feed URL 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.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
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
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Genereer nieuwe API sleutel
profile.generate_new_api_key_info=Het veranderen van het wachtwoord genereert een nieuwe API sleutel profile.generate_new_api_key_info=Het veranderen van het wachtwoord genereert een nieuwe API sleutel
profile.opml_export=OPML export profile.opml_export=OPML export
profile.delete_account=Verwijder account profile.delete_account=Verwijder account
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=REST API about.rest_api=REST API
about.keyboard_shortcuts=Sneltoetsen about.keyboard_shortcuts=Sneltoetsen

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
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Sorter etter dato ny/gamal
toolbar.titles_only=Berre titlar toolbar.titles_only=Berre titlar
toolbar.expanded_view=Utvida visning toolbar.expanded_view=Utvida visning
toolbar.mark_all_as_read=Merk alle som lesne toolbar.mark_all_as_read=Merk alle som lesne
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=Artiklar eldre enn éin dag toolbar.mark_all_older_day=Artiklar eldre enn éin dag
toolbar.mark_all_older_week=Artiklar eldre enn éi veke toolbar.mark_all_older_week=Artiklar eldre enn éi veke
toolbar.mark_all_older_two_weeks=Artiklar eldre enn to veker toolbar.mark_all_older_two_weeks=Artiklar eldre enn to veker
@@ -66,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
@@ -83,7 +87,10 @@ details.queued_for_refresh=I kø for oppdatering
details.feed_url=URL for abonnement 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.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
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
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Generer ny API-nykel
profile.generate_new_api_key_info=Endring av passord vil generere ein ny API-nykel profile.generate_new_api_key_info=Endring av passord vil generere ein ny API-nykel
profile.opml_export=OPML-eksport profile.opml_export=OPML-eksport
profile.delete_account=Slett brukar profile.delete_account=Slett brukar
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=REST API about.rest_api=REST API
about.keyboard_shortcuts=Hurtigtastar about.keyboard_shortcuts=Hurtigtastar

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
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Sortuj od najnowszego/najstarszego
toolbar.titles_only=Widok listy toolbar.titles_only=Widok listy
toolbar.expanded_view=Widok rozwinięty toolbar.expanded_view=Widok rozwinięty
toolbar.mark_all_as_read=Oznacz wszystko jako przeczytane toolbar.mark_all_as_read=Oznacz wszystko jako przeczytane
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=Elementy starsze niż dzień toolbar.mark_all_older_day=Elementy starsze niż dzień
toolbar.mark_all_older_week=Elementy starsze niż tydzień toolbar.mark_all_older_week=Elementy starsze niż tydzień
toolbar.mark_all_older_two_weeks=Elementy starsze niż dwa tygodnie toolbar.mark_all_older_two_weeks=Elementy starsze niż dwa tygodnie
@@ -66,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
@@ -83,7 +87,10 @@ details.queued_for_refresh=W kolejce do odświeżenia
details.feed_url=URL kanału 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.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
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
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Wygeneruj nowy klucz API
profile.generate_new_api_key_info=Zmiana hasła spowoduje wygenerowanie nowego klucza API profile.generate_new_api_key_info=Zmiana hasła spowoduje wygenerowanie nowego klucza API
profile.opml_export=Eksportuj do pliku OPML profile.opml_export=Eksportuj do pliku OPML
profile.delete_account=Usuń konto profile.delete_account=Usuń konto
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=REST API about.rest_api=REST API
about.keyboard_shortcuts=Skróty klawiszowe about.keyboard_shortcuts=Skróty klawiszowe

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
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Ordenar por data cresc/decres
toolbar.titles_only=Somente títulos toolbar.titles_only=Somente títulos
toolbar.expanded_view=Modo Expandido toolbar.expanded_view=Modo Expandido
toolbar.mark_all_as_read=Marcar tudo como lido toolbar.mark_all_as_read=Marcar tudo como lido
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=Itens mais antigos que um dia toolbar.mark_all_older_day=Itens mais antigos que um dia
toolbar.mark_all_older_week=Itens mais antigos que uma semana toolbar.mark_all_older_week=Itens mais antigos que uma semana
toolbar.mark_all_older_two_weeks=Itens mais antigos que duas semanas toolbar.mark_all_older_two_weeks=Itens mais antigos que duas semanas
@@ -66,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
@@ -83,7 +87,10 @@ details.queued_for_refresh=Na fila para atualizar
details.feed_url=URL do feed 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.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
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
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Gerar nova chave de API
profile.generate_new_api_key_info=Mudar a senha irá gerar uma nova chave de API profile.generate_new_api_key_info=Mudar a senha irá gerar uma nova chave de API
profile.opml_export=Exportar OPML profile.opml_export=Exportar OPML
profile.delete_account=Excluir conta profile.delete_account=Excluir conta
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=API REST about.rest_api=API REST
about.keyboard_shortcuts=Atalhos de teclado about.keyboard_shortcuts=Atalhos de teclado

View File

@@ -6,6 +6,7 @@ global.download=Скачать
global.link=Ссылка global.link=Ссылка
global.bookmark=Закладка global.bookmark=Закладка
global.close=Закрыть global.close=Закрыть
global.tags=Теги
tree.subscribe=Подписаться tree.subscribe=Подписаться
tree.import=Импорт tree.import=Импорт
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Сначала новые/старые
toolbar.titles_only=Только заголовки toolbar.titles_only=Только заголовки
toolbar.expanded_view=Развёрнутый вид toolbar.expanded_view=Развёрнутый вид
toolbar.mark_all_as_read=Отметить всё как прочитанное toolbar.mark_all_as_read=Отметить всё как прочитанное
toolbar.mark_all_older_12_hours=Записи старше 12-и часов
toolbar.mark_all_older_day=Записи старше суток toolbar.mark_all_older_day=Записи старше суток
toolbar.mark_all_older_week=Записи старше недели toolbar.mark_all_older_week=Записи старше недели
toolbar.mark_all_older_two_weeks=Записи старше двух недель toolbar.mark_all_older_two_weeks=Записи старше двух недель
@@ -52,8 +54,8 @@ view.error_while_loading_feed=Не удалось загрузить ленту
view.keep_unread=Оставить непрочитанным view.keep_unread=Оставить непрочитанным
view.no_unread_items=нет непрочитанных записей. view.no_unread_items=нет непрочитанных записей.
view.mark_up_to_here=Отметить прочитанным до сюда view.mark_up_to_here=Отметить прочитанным до сюда
view.search_for=searching for: ####### Needs translation view.search_for=искать:
view.no_search_results=No match found for the requested keywords ####### Needs translation view.no_search_results=По данному запросу ничего не найдено.
feedsearch.hint=Введите подписку... feedsearch.hint=Введите подписку...
feedsearch.help=Используйте клавишу ввода для выбора и стрелки для перемещения. feedsearch.help=Используйте клавишу ввода для выбора и стрелки для перемещения.
@@ -66,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=Скорость прокрутки при навигации между записями (в миллисекундах)
settings.scroll_speed.help=смените на 0 чтобы выключить
settings.theme=Тема settings.theme=Тема
settings.submit_your_theme=Добавьте свою тему settings.submit_your_theme=Добавьте свою тему
settings.custom_css=Собственная CSS settings.custom_css=Собственная CSS
@@ -77,13 +81,16 @@ details.name=Название
details.category=Категория details.category=Категория
details.position=Позиция details.position=Позиция
details.last_refresh=Последнее обновление details.last_refresh=Последнее обновление
details.message=Last refresh message ####### Needs translation details.message=Сообщение последнего обновления
details.next_refresh=Следующее обновление details.next_refresh=Следующее обновление
details.queued_for_refresh=В очереди на обновление details.queued_for_refresh=В очереди на обновление
details.feed_url=Адрес ленты details.feed_url=Адрес ленты
details.generate_api_key_first=Сначала сгенерируйте API-ключ в вашем профиле. details.generate_api_key_first=Сначала сгенерируйте API-ключ в вашем профиле.
details.unsubscribe=Отписаться details.unsubscribe=Отписаться
details.unsubscribe_confirmation=Подтвердить отписку от этой ленты? Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Подтвердить удаление этой категории?
details.category_details=Информация о категории details.category_details=Информация о категории
details.tag_details=Детали тега
details.parent_category=Родительская категория details.parent_category=Родительская категория
profile.user_name=Имя пользователя profile.user_name=Имя пользователя
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Сгенерировать новый API-ключ
profile.generate_new_api_key_info=После изменения пароля, API-ключ изменится profile.generate_new_api_key_info=После изменения пароля, API-ключ изменится
profile.opml_export=Экспорт OPML profile.opml_export=Экспорт OPML
profile.delete_account=Удалить аккаунт profile.delete_account=Удалить аккаунт
profile.delete_account_confirmation=Удалить ваш аккаунт? Назад пути не будет!
about.rest_api=REST API about.rest_api=REST API
about.keyboard_shortcuts=Горячие клавиши about.keyboard_shortcuts=Горячие клавиши

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=Tagy
tree.subscribe=Odoberať tree.subscribe=Odoberať
tree.import=Importovať tree.import=Importovať
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Zoradiť podľa najnovšieho/najstaršieho
toolbar.titles_only=Náhľad titulkov toolbar.titles_only=Náhľad titulkov
toolbar.expanded_view=Rozšírený náhľad toolbar.expanded_view=Rozšírený náhľad
toolbar.mark_all_as_read=Označiť všetky ako prečítané toolbar.mark_all_as_read=Označiť všetky ako prečítané
toolbar.mark_all_older_12_hours=Položky staršie ako 12 hodín
toolbar.mark_all_older_day=Položky staršie ako deň toolbar.mark_all_older_day=Položky staršie ako deň
toolbar.mark_all_older_week=Položky staršie ako týždeň toolbar.mark_all_older_week=Položky staršie ako týždeň
toolbar.mark_all_older_two_weeks=Položky staršie ako dva týždne toolbar.mark_all_older_two_weeks=Položky staršie ako dva týždne
@@ -66,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=Rýchlosť skrolovania—pohybu medzi položkami (v milisekundách)
settings.scroll_speed.help=nastavte 0 pre deaktiváciu
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)
@@ -83,7 +87,10 @@ details.queued_for_refresh=Vo fronte
details.feed_url=URL RSS zdroja 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.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
details.category_details=Detaily kategórie details.category_details=Detaily kategórie
details.tag_details=Detaily tagu
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
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Vygenerovať nový API kľúč
profile.generate_new_api_key_info=Zmenou hesla vygenerujete nový API kľúč profile.generate_new_api_key_info=Zmenou hesla vygenerujete nový API kľúč
profile.opml_export=exportovať do formátu OPML profile.opml_export=exportovať do formátu OPML
profile.delete_account=Odstrániť účet profile.delete_account=Odstrániť účet
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=REST API about.rest_api=REST API
about.keyboard_shortcuts=Klávesové skratky about.keyboard_shortcuts=Klávesové skratky
@@ -146,3 +154,4 @@ about.shortcuts.font_size=zmeniť veľkosť písma pre vybranú položku
about.shortcuts.go_to_all=zobraziť všetky položky about.shortcuts.go_to_all=zobraziť všetky položky
about.shortcuts.go_to_starred=zobraziť obľúbené položiek about.shortcuts.go_to_starred=zobraziť obľúbené položiek
about.shortcuts.feed_search=presun na odoberaný RSS zdroj vložením jeho názvu about.shortcuts.feed_search=presun na odoberaný RSS zdroj vložením jeho názvu

View File

@@ -6,6 +6,7 @@ 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=Taggar
tree.subscribe=Prenumerera tree.subscribe=Prenumerera
tree.import=Importera tree.import=Importera
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Sortera efter datum stigande/fallande
toolbar.titles_only=Endast titlar toolbar.titles_only=Endast titlar
toolbar.expanded_view=Expanderad vy toolbar.expanded_view=Expanderad vy
toolbar.mark_all_as_read=Markera alla som lästa toolbar.mark_all_as_read=Markera alla som lästa
toolbar.mark_all_older_12_hours=Poster äldre än 12 timmar
toolbar.mark_all_older_day=Poster äldre än en dag toolbar.mark_all_older_day=Poster äldre än en dag
toolbar.mark_all_older_week=Poster äldre än en vecka toolbar.mark_all_older_week=Poster äldre än en vecka
toolbar.mark_all_older_two_weeks=Poster äldre än två veckor toolbar.mark_all_older_two_weeks=Poster äldre än två veckor
@@ -66,6 +68,8 @@ settings.general.show_unread=Visa prenumerationer och kategorier utan olästa po
settings.general.social_buttons=Visa delningsknappar settings.general.social_buttons=Visa delningsknappar
settings.general.scroll_marks=I expanderad vy, markera poster som lästa genom att scrolla förbi dem settings.general.scroll_marks=I expanderad vy, markera poster som lästa genom att scrolla förbi dem
settings.appearance=Utseende settings.appearance=Utseende
settings.scroll_speed=Scrollhastighet under navigation mellan poster (i millisekunder)
settings.scroll_speed.help=ställ på 0 för att avaktivera
settings.theme=Tema settings.theme=Tema
settings.submit_your_theme=Skicka in ditt tema settings.submit_your_theme=Skicka in ditt tema
settings.custom_css=Anpassad CSS settings.custom_css=Anpassad CSS
@@ -83,7 +87,10 @@ details.queued_for_refresh=I kö för uppdatering
details.feed_url=Prenumerationens URL details.feed_url=Prenumerationens URL
details.generate_api_key_first=Skapa en API-nyckel på din profil först. details.generate_api_key_first=Skapa en API-nyckel på din profil först.
details.unsubscribe=Avprenumerera details.unsubscribe=Avprenumerera
details.unsubscribe_confirmation=Är du säker på att du vill avprenumerera?
details.delete_category_confirmation=Är du säker på att du vill ta bort denna kategori?
details.category_details=Kategoridetaljer details.category_details=Kategoridetaljer
details.tag_details=Taggdetaljer
details.parent_category=Överordnad kategori details.parent_category=Överordnad kategori
profile.user_name=Användarnamn profile.user_name=Användarnamn
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Skapa ny API-nyckel
profile.generate_new_api_key_info=Lösenordsbyte skapar ny API-nyckel profile.generate_new_api_key_info=Lösenordsbyte skapar ny API-nyckel
profile.opml_export=OPML-export profile.opml_export=OPML-export
profile.delete_account=Radera konto profile.delete_account=Radera konto
profile.delete_account_confirmation=Vill du ta bort ditt konto? Det försvinner för alltid!
about.rest_api=REST-API about.rest_api=REST-API
about.keyboard_shortcuts=Tangentbordsgenvägar about.keyboard_shortcuts=Tangentbordsgenvägar

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
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Tarihe göre sırala artan/azalan
toolbar.titles_only=Sadece başlıklar toolbar.titles_only=Sadece başlıklar
toolbar.expanded_view=Genişletilmiş görünüm toolbar.expanded_view=Genişletilmiş görünüm
toolbar.mark_all_as_read=Tümünü okundu işaretle toolbar.mark_all_as_read=Tümünü okundu işaretle
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=Bir günden eski yazılar toolbar.mark_all_older_day=Bir günden eski yazılar
toolbar.mark_all_older_week=Bir haftadan eski yazılar toolbar.mark_all_older_week=Bir haftadan eski yazılar
toolbar.mark_all_older_two_weeks=İki haftadan eski yazılar toolbar.mark_all_older_two_weeks=İki haftadan eski yazılar
@@ -66,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
@@ -83,7 +87,10 @@ details.queued_for_refresh=Yenilenmek üzere kuyrukta
details.feed_url=Yayın URL'si 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.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
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ı
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Yeni bir API anahtarı oluştur
profile.generate_new_api_key_info=Şifre değiştirmek API anahtarının da değiştirilmesine neden olcak. profile.generate_new_api_key_info=Şifre değiştirmek API anahtarının da değiştirilmesine neden olcak.
profile.opml_export=OPML dışa aktar profile.opml_export=OPML dışa aktar
profile.delete_account=Hesabı sil profile.delete_account=Hesabı sil
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=REST API about.rest_api=REST API
about.keyboard_shortcuts=Klavye kısayolları about.keyboard_shortcuts=Klavye kısayolları

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=导入
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=按日期升序/降序排序
toolbar.titles_only=仅显示标题 toolbar.titles_only=仅显示标题
toolbar.expanded_view=显示内容 toolbar.expanded_view=显示内容
toolbar.mark_all_as_read=标记所有为已读 toolbar.mark_all_as_read=标记所有为已读
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=早于一天的条目 toolbar.mark_all_older_day=早于一天的条目
toolbar.mark_all_older_week=早于一周的条目 toolbar.mark_all_older_week=早于一周的条目
toolbar.mark_all_older_two_weeks=早于两周的条目 toolbar.mark_all_older_two_weeks=早于两周的条目
@@ -66,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 样式
@@ -83,7 +87,10 @@ details.queued_for_refresh=放入等待刷新的队列
details.feed_url=订阅地址 details.feed_url=订阅地址
details.generate_api_key_first=在您的配置文件中首先生成一个 API 密钥。 details.generate_api_key_first=在您的配置文件中首先生成一个 API 密钥。
details.unsubscribe=取消订阅 details.unsubscribe=取消订阅
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
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=用户名
@@ -98,6 +105,7 @@ profile.generate_new_api_key=生成一个新的 API 密钥
profile.generate_new_api_key_info=修改密码将会生成一个新的的 API 密钥 profile.generate_new_api_key_info=修改密码将会生成一个新的的 API 密钥
profile.opml_export=导出 OPML profile.opml_export=导出 OPML
profile.delete_account=删除帐号 profile.delete_account=删除帐号
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=重置 API about.rest_api=重置 API
about.keyboard_shortcuts=快捷键 about.keyboard_shortcuts=快捷键

View File

@@ -8,9 +8,9 @@
# tomee.jaxws.subcontext = webservices # tomee.jaxws.subcontext = webservices
# tomee.jaxws.oldsubcontext = false # tomee.jaxws.oldsubcontext = false
# openejb.system.apps = true
# openejb.servicemanager.enabled = true # openejb.servicemanager.enabled = true
# openejb.jmx.active = false
# openejb.descriptors.output = false # openejb.descriptors.output = false
# openejb.strict.interface.declaration = false # openejb.strict.interface.declaration = false
# openejb.conf.file = conf/tomee.xml # openejb.conf.file = conf/tomee.xml
@@ -31,7 +31,6 @@
# org.apache.openejb.server.webservices.saaj.provider = # org.apache.openejb.server.webservices.saaj.provider =
# openejb.nobanner = true # openejb.nobanner = true
# openejb.offline = false # openejb.offline = false
# openejb.jmx.active = true
# openejb.exclude-include.order = include-exclude # openejb.exclude-include.order = include-exclude
# openejb.additional.exclude = # openejb.additional.exclude =
# openejb.additional.include = # openejb.additional.include =
@@ -46,5 +45,9 @@
# javax.persistence.jtaDataSource = # javax.persistence.jtaDataSource =
# javax.persistence.nonJtaDataSource = # javax.persistence.nonJtaDataSource =
openejb.system.apps = false
openejb.jmx.active = false
AsynchronousPool.CorePoolSize = 1 AsynchronousPool.CorePoolSize = 1
AsynchronousPool.MaximumPoolSize = 100 AsynchronousPool.MaximumPoolSize = 100
openejb.stats.interceptor.disable = true

View File

@@ -7,6 +7,7 @@
UserName = sa UserName = sa
Password = Password =
MaxActive = 50 MaxActive = 50
DataSourceCreator bonecp
</Resource> </Resource>
<!-- <!--
@@ -16,6 +17,7 @@
UserName cf UserName cf
Password cf Password cf
MaxActive 50 MaxActive 50
DataSourceCreator bonecp
</Resource> </Resource>
--> -->
@@ -26,6 +28,7 @@
UserName cf UserName cf
Password cf Password cf
MaxActive 50 MaxActive 50
DataSourceCreator bonecp
</Resource> </Resource>
--> -->
@@ -36,6 +39,7 @@
UserName cf UserName cf
Password cf Password cf
MaxActive 50 MaxActive 50
DataSourceCreator bonecp
</Resource> </Resource>
--> -->

View File

@@ -0,0 +1,7 @@
<?xml version="1.0"?>
<scan>
<packages>
<package>com.commafeed.backend</package>
<package>com.commafeed.frontend.rest</package>
</packages>
</scan>

View File

@@ -3,24 +3,29 @@
<group name="lib"> <group name="lib">
<js minimize="false">/vendor/jquery/*.js</js> <js minimize="false">/vendor/jquery/*.js</js>
<js minimize="false">/vendor/select2/*.js</js>
<js minimize="false">/vendor/lodash/*.js</js> <js minimize="false">/vendor/lodash/*.js</js>
<js minimize="false">/vendor/jqueryui/*.js</js> <js minimize="false">/vendor/jqueryui/*.js</js>
<js minimize="false">/vendor/jquery-mousewheel/*.js</js> <js minimize="false">/vendor/jquery-mousewheel/*.js</js>
<js minimize="false">/vendor/bootstrap/*.js</js> <js minimize="false">/vendor/bootstrap/*.js</js>
<js minimize="false">/vendor/angularjs/*.js</js> <js minimize="false">/vendor/angularjs/*.js</js>
<js minimize="false">/vendor/angularui/*.js</js> <js minimize="false">/vendor/ui-utils/*.js</js>
<js minimize="false">/vendor/angularui-bootstrap/*.js</js> <js minimize="false">/vendor/ui-select2/*.js</js>
<js minimize="false">/vendor/angularui-state/*.js</js> <js minimize="false">/vendor/ui-bootstrap/*.js</js>
<js minimize="false">/vendor/ui-router/*.js</js>
<js minimize="false">/vendor/mousetrap/*.js</js> <js minimize="false">/vendor/mousetrap/*.js</js>
<js minimize="false">/vendor/nggrid/*.js</js> <js minimize="false">/vendor/nggrid/*.js</js>
<js minimize="false">/vendor/nginfinitescroll/*.js</js> <js minimize="false">/vendor/nginfinitescroll/*.js</js>
<js minimize="false">/vendor/spinjs/*.js</js> <js minimize="false">/vendor/spinjs/*.js</js>
<js minimize="false">/vendor/momentjs/*.js</js> <js minimize="false">/vendor/momentjs/*.js</js>
<js minimize="false">/vendor/devicejs/*.js</js>
<css minimize="false">/vendor/jqueryui/*.css</css>
<css minimize="false">/vendor/select2/*.css</css>
<css minimize="false">/vendor/bootstrap/*.css</css> <css minimize="false">/vendor/bootstrap/*.css</css>
<css minimize="false">/vendor/angularui/*.css</css>
<css minimize="false">/vendor/fontawesome/css/*.css</css> <css minimize="false">/vendor/fontawesome/css/*.css</css>
<css minimize="false">/vendor/zocial/*.css</css> <css minimize="false">/vendor/zocial/*.css</css>
<css minimize="false">/vendor/readabilicons/css/*.css</css>
<css minimize="false">/vendor/nggrid/*.css</css> <css minimize="false">/vendor/nggrid/*.css</css>
</group> </group>

View File

@@ -1,86 +1,91 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>Swagger UI</title> <title>Swagger UI</title>
<link href='//fonts.googleapis.com/css?family=Droid+Sans:400,700' rel='stylesheet' type='text/css'/> <link href='//fonts.googleapis.com/css?family=Droid+Sans:400,700' rel='stylesheet' type='text/css' />
<link href='css/hightlight.default.css' media='screen' rel='stylesheet' type='text/css'/> <link href='css/hightlight.default.css' media='screen' rel='stylesheet' type='text/css' />
<link href='css/screen.css' media='screen' rel='stylesheet' type='text/css'/> <link href='css/screen.css' media='screen' rel='stylesheet' type='text/css' />
<link href='css/custom.css' media='screen' rel='stylesheet' type='text/css'/> <link href='css/custom.css' media='screen' rel='stylesheet' type='text/css' />
<script src='lib/jquery-1.8.0.min.js' type='text/javascript'></script> <script src='lib/jquery-1.8.0.min.js' type='text/javascript'></script>
<script src='lib/jquery.slideto.min.js' type='text/javascript'></script> <script src='lib/jquery.slideto.min.js' type='text/javascript'></script>
<script src='lib/jquery.wiggle.min.js' type='text/javascript'></script> <script src='lib/jquery.wiggle.min.js' type='text/javascript'></script>
<script src='lib/jquery.ba-bbq.min.js' type='text/javascript'></script> <script src='lib/jquery.ba-bbq.min.js' type='text/javascript'></script>
<script src='lib/handlebars-1.0.rc.1.js' type='text/javascript'></script> <script src='lib/handlebars-1.0.rc.1.js' type='text/javascript'></script>
<script src='lib/underscore-min.js' type='text/javascript'></script> <script src='lib/underscore-min.js' type='text/javascript'></script>
<script src='lib/backbone-min.js' type='text/javascript'></script> <script src='lib/backbone-min.js' type='text/javascript'></script>
<script src='lib/swagger.js' type='text/javascript'></script> <script src='lib/swagger.js' type='text/javascript'></script>
<script src='swagger-ui.js' type='text/javascript'></script> <script src='swagger-ui.js' type='text/javascript'></script>
<script src='lib/highlight.7.3.pack.js' type='text/javascript'></script> <script src='lib/highlight.7.3.pack.js' type='text/javascript'></script>
<script type="text/javascript"> <script type="text/javascript">
$(function () { $(function() {
var url = window.location.href; var url = window.location.href;
if (url.indexOf('#')> -1) { if (url.indexOf('#') > -1) {
url = url.substring(0, url.lastIndexOf('#')); url = url.substring(0, url.lastIndexOf('#'));
} }
url = url.substring(0, url.length - 1); url = url.substring(0, url.length - 1);
url = url.substring(0, url.lastIndexOf('/')); url = url.substring(0, url.lastIndexOf('/'));
var link = $('#title-base-url-link') var link = $('#title-base-url-link');
link.attr('href', url + '/rest'); link.attr('href', url + '/rest');
link.html(link.attr('href')); link.html(link.attr('href'));
url = url + '/api/api-docs/resources'; url = url + '/api/api-docs/resources';
window.swaggerUi = new SwaggerUi({ window.swaggerUi = new SwaggerUi({
discoveryUrl:url, discoveryUrl : url,
apiKey:"", apiKey : "",
dom_id:"swagger-ui-container", dom_id : "swagger-ui-container",
supportHeaderParams: false, supportHeaderParams : false,
supportedSubmitMethods: ['get', 'post', 'put', 'delete'], supportedSubmitMethods : ['get', 'post', 'put', 'delete'],
onComplete: function(swaggerApi, swaggerUi){ onComplete : function(swaggerApi, swaggerUi) {
if(console) { if (console) {
console.log("Loaded SwaggerUI") console.log("Loaded SwaggerUI");
console.log(swaggerApi); console.log(swaggerApi);
console.log(swaggerUi); console.log(swaggerUi);
} }
$('pre code').each(function(i, e) {hljs.highlightBlock(e)}); $('pre code').each(function(i, e) {
}, hljs.highlightBlock(e);
onFailure: function(data) { });
if(console) { },
console.log("Unable to Load SwaggerUI"); onFailure : function(data) {
console.log(data); if (console) {
} console.log("Unable to Load SwaggerUI");
}, console.log(data);
docExpansion: "none" }
}); },
docExpansion : "none"
});
window.swaggerUi.load(); window.swaggerUi.load();
}); });
</script>
</script>
</head> </head>
<body> <body>
<div id='header'> <div id='header'>
<div class="swagger-ui-wrap"> <div class="swagger-ui-wrap">
<a id="logo" href="../" style="padding-left: 0px">CommaFeed API</a> <a id="logo" href="../" style="padding-left: 0px">CommaFeed API</a>
<form id='api_selector'> <form id='api_selector'>
<div class='input'><input placeholder="http://example.com/api" id="input_baseUrl" name="baseUrl" <div class='input'>
type="text"/></div> <input placeholder="http://example.com/api" id="input_baseUrl" name="baseUrl" type="text" />
<div class='input'><a id="explore" href="#">Explore</a></div> </div>
</form> <div class='input'>
</div> <a id="explore" href="#">Explore</a>
</div> </div>
</form>
</div>
</div>
<div id="message-bar" class="swagger-ui-wrap"> <div id="message-bar" class="swagger-ui-wrap">&nbsp;</div>
&nbsp;
</div>
<h3 style="text-align: center">Authentication is required to access the REST API. Use HTTP Basic Authentication to authenticate yourself.</h3> <h3 style="text-align: center">Authentication is required to access the REST API. Use HTTP Basic Authentication to authenticate
<h3 style="text-align: center">The base URL of the API is <a id="title-base-url-link"></a>.</h3> yourself.</h3>
<div id="swagger-ui-container" class="swagger-ui-wrap"> <h3 style="text-align: center">
The base URL of the API is
</div> <a id="title-base-url-link"></a>
.
</h3>
<div id="swagger-ui-container" class="swagger-ui-wrap"></div>
</body> </body>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 305 KiB

After

Width:  |  Height:  |  Size: 193 KiB

View File

@@ -30,36 +30,16 @@ module.run(['$rootScope', function($rootScope) {
}); });
}]); }]);
module.controller('SubscribeCtrl', ['$scope', 'FeedService', 'CategoryService', 'MobileService', module.controller('SubscribeCtrl', ['$scope', '$location', 'FeedService', 'CategoryService', 'MobileService',
function($scope, FeedService, CategoryService, MobileService) { function($scope, $location, FeedService, CategoryService, MobileService) {
$scope.opts = { $scope.sub = {
backdropFade : true, categoryId : 'all'
dialogFade : true
}; };
$scope.isOpen = false;
$scope.isOpenImport = false;
$scope.sub = {};
$scope.CategoryService = CategoryService; $scope.CategoryService = CategoryService;
$scope.MobileService = MobileService; $scope.MobileService = MobileService;
$scope.search = function() {
$scope.$emit('emitFeedSearch');
};
$scope.open = function() {
$scope.sub = {
categoryId : $scope.sub.categoryId || 'all'
};
$scope.isOpen = true;
};
$scope.close = function() {
$scope.isOpen = false;
};
// 'ok', 'loading' or 'failed' // 'ok', 'loading' or 'failed'
$scope.state = 'ok'; $scope.state = 'ok';
$scope.urlChanged = function() { $scope.urlChanged = function() {
@@ -88,59 +68,97 @@ module.controller('SubscribeCtrl', ['$scope', 'FeedService', 'CategoryService',
} }
FeedService.subscribe($scope.sub, function() { FeedService.subscribe($scope.sub, function() {
CategoryService.init(); CategoryService.init();
$scope.close(); $location.path('/');
}, function(data) { }, function(data) {
$scope.state = 'failed'; $scope.state = 'failed';
$scope.sub.title = 'ERROR: ' + data.data; $scope.sub.title = 'ERROR: ' + data.data;
}); });
}; };
$scope.openImport = function() { $scope.back = function() {
$scope.isOpenImport = true; $location.path('/');
}; };
}]);
$scope.closeImport = function() { module.controller('NewCategoryCtrl', ['$scope', '$location', 'FeedService', 'CategoryService', 'MobileService',
$scope.isOpenImport = false; function($scope, $location, FeedService, CategoryService, MobileService) {
};
$scope.cat = {}; $scope.CategoryService = CategoryService;
$scope.MobileService = MobileService;
$scope.openCategory = function() { $scope.cat = {
$scope.isOpenCategory = true; parentId : 'all'
$scope.cat = {
parentId : 'all'
};
};
$scope.closeCategory = function() {
$scope.isOpenCategory = false;
}; };
$scope.saveCategory = function() { $scope.saveCategory = function() {
CategoryService.add($scope.cat, function() { CategoryService.add($scope.cat, function() {
CategoryService.init(); CategoryService.init();
}); });
$scope.closeCategory(); $location.path('/');
};
$scope.back = function() {
$location.path('/');
}; };
}]); }]);
module.controller('CategoryTreeCtrl', ['$scope', '$timeout', '$stateParams', '$window', '$location', '$state', '$route', 'CategoryService', module.controller('ImportCtrl', ['$scope', '$location', 'FeedService', 'CategoryService', 'MobileService',
function($scope, $location, FeedService, CategoryService, MobileService) {
$scope.back = function() {
$location.path('/');
};
}]);
module.controller('CategoryTreeCtrl', [
'$scope',
'$timeout',
'$stateParams',
'$window',
'$location',
'$state',
'$route',
'CategoryService',
'AnalyticsService', 'AnalyticsService',
function($scope, $timeout, $stateParams, $window, $location, $state, $route, CategoryService, AnalyticsService) { 'EntryService',
'MobileService',
function($scope, $timeout, $stateParams, $window, $location, $state, $route, CategoryService, AnalyticsService, EntryService,
MobileService) {
$scope.selectedType = $stateParams._type; $scope.selectedType = $stateParams._type;
$scope.selectedId = $stateParams._id; $scope.selectedId = $stateParams._id;
$scope.EntryService = EntryService;
$scope.MobileService = MobileService;
$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.$on('$stateChangeSuccess', function() { $scope.$on('$stateChangeSuccess', function() {
$scope.selectedType = $stateParams._type; $scope.selectedType = $stateParams._type;
$scope.selectedId = $stateParams._id; $scope.selectedId = $stateParams._id;
}); });
$scope.resizeCallback = function(event, ui) {
$('.main-content').css('margin-left', $(ui.element).outerWidth(true) + 'px');
};
$timeout(function refreshTree() { $timeout(function refreshTree() {
AnalyticsService.track(); AnalyticsService.track();
CategoryService.refresh(function() { CategoryService.refresh(function() {
@@ -201,7 +219,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;
@@ -266,8 +284,8 @@ module.controller('CategoryTreeCtrl', ['$scope', '$timeout', '$stateParams', '$w
}); });
}]); }]);
module.controller('FeedDetailsCtrl', ['$scope', '$state', '$stateParams', 'FeedService', 'CategoryService', 'ProfileService', '$dialog', module.controller('FeedDetailsCtrl', ['$scope', '$state', '$stateParams', 'FeedService', 'CategoryService', 'ProfileService',
function($scope, $state, $stateParams, FeedService, CategoryService, ProfileService, $dialog) { function($scope, $state, $stateParams, FeedService, CategoryService, ProfileService) {
$scope.CategoryService = CategoryService; $scope.CategoryService = CategoryService;
$scope.user = ProfileService.get(); $scope.user = ProfileService.get();
@@ -288,30 +306,15 @@ module.controller('FeedDetailsCtrl', ['$scope', '$state', '$stateParams', 'FeedS
$scope.unsubscribe = function() { $scope.unsubscribe = function() {
var sub = $scope.sub; var sub = $scope.sub;
var title = 'Unsubscribe'; var data = {
var msg = 'Unsubscribe from ' + sub.name + '?'; id : sub.id
var btns = [{ };
result : 'cancel', FeedService.unsubscribe(data, function() {
label : 'Cancel' CategoryService.init();
}, { });
result : 'ok', $state.transitionTo('feeds.view', {
label : 'OK', _id : 'all',
cssClass : 'btn-primary' _type : 'category'
}];
$dialog.messageBox(title, msg, btns).open().then(function(result) {
if (result == 'ok') {
var data = {
id : sub.id
};
FeedService.unsubscribe(data, function() {
CategoryService.init();
});
$state.transitionTo('feeds.view', {
_id : 'all',
_type : 'category'
});
}
}); });
}; };
@@ -333,7 +336,7 @@ module.controller('FeedDetailsCtrl', ['$scope', '$state', '$stateParams', 'FeedS
}]); }]);
module.controller('CategoryDetailsCtrl', ['$scope', '$state', '$stateParams', 'FeedService', 'CategoryService', 'ProfileService', module.controller('CategoryDetailsCtrl', ['$scope', '$state', '$stateParams', 'FeedService', 'CategoryService', 'ProfileService',
'$dialog', function($scope, $state, $stateParams, FeedService, CategoryService, ProfileService, $dialog) { function($scope, $state, $stateParams, FeedService, CategoryService, ProfileService) {
$scope.CategoryService = CategoryService; $scope.CategoryService = CategoryService;
$scope.user = ProfileService.get(); $scope.user = ProfileService.get();
@@ -355,7 +358,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 = {
@@ -380,29 +383,14 @@ module.controller('CategoryDetailsCtrl', ['$scope', '$state', '$stateParams', 'F
$scope.deleteCategory = function() { $scope.deleteCategory = function() {
var category = $scope.category; var category = $scope.category;
var title = 'Delete category'; CategoryService.remove({
var msg = 'Delete category ' + category.name + ' ?'; id : category.id
var btns = [{ }, function() {
result : 'cancel', CategoryService.init();
label : 'Cancel' });
}, { $state.transitionTo('feeds.view', {
result : 'ok', _id : 'all',
label : 'OK', _type : 'category'
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'
});
}
}); });
}; };
@@ -423,6 +411,21 @@ module.controller('CategoryDetailsCtrl', ['$scope', '$state', '$stateParams', 'F
}; };
}]); }]);
module.controller('TagDetailsCtrl', ['$scope', '$state', '$stateParams', 'FeedService', 'CategoryService', 'ProfileService',
function($scope, $state, $stateParams, FeedService, CategoryService, ProfileService) {
$scope.CategoryService = CategoryService;
$scope.user = ProfileService.get();
$scope.tag = $stateParams._id;
$scope.back = function() {
$state.transitionTo('feeds.view', {
_id : $scope.tag,
_type : 'tag'
});
};
}]);
module.controller('ToolbarCtrl', [ module.controller('ToolbarCtrl', [
'$scope', '$scope',
'$http', '$http',
@@ -502,6 +505,10 @@ module.controller('ToolbarCtrl', [
markAll(); markAll();
}; };
$scope.markAll12Hours = function() {
markAll(new Date().getTime() - 43200000);
};
$scope.markAllDay = function() { $scope.markAllDay = function() {
markAll(new Date().getTime() - 86400000); markAll(new Date().getTime() - 86400000);
}; };
@@ -515,9 +522,10 @@ module.controller('ToolbarCtrl', [
}; };
$scope.search = function() { $scope.search = function() {
$location.search('q', $scope.keywords); var keywords = this.keywords;
$location.search('q', keywords);
$scope.$emit('emitEntrySearch', { $scope.$emit('emitEntrySearch', {
keywords : $scope.keywords keywords : keywords
}); });
}; };
$scope.showButtons = function() { $scope.showButtons = function() {
@@ -570,7 +578,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;
@@ -702,6 +710,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;
@@ -714,7 +728,10 @@ module.controller('FeedListCtrl', [
$scope.busy = true; $scope.busy = true;
var limit = $scope.limit; var limit = $scope.limit;
var offset = SettingsService.settings.readingMode == 'all' ? $scope.entries.length : _.where($scope.entries, {
var read_shown = SettingsService.settings.readingMode === 'all' || $scope.selectedType === 'tag'
|| ($scope.selectedType === 'category' && $scope.selectedId === 'starred');
var offset = read_shown ? $scope.entries.length : _.where($scope.entries, {
read : false read : false
}).length; }).length;
if ($scope.entries.length === 0) { if ($scope.entries.length === 0) {
@@ -729,7 +746,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
@@ -746,15 +763,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;
@@ -781,10 +806,11 @@ module.controller('FeedListCtrl', [
return; return;
} else { } else {
var scrollTop = elemTop - $('#toolbar').outerHeight(); var scrollTop = elemTop - $('#toolbar').outerHeight();
var speed = SettingsService.settings.scrollSpeed;
watch_scrolling = false; watch_scrolling = false;
$('html, body').animate({ $('html, body').animate({
scrollTop : scrollTop scrollTop : scrollTop
}, 400, 'swing', function() { }, speed, 'swing', function() {
watch_scrolling = true; watch_scrolling = true;
}); });
} }
@@ -803,7 +829,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()) {
@@ -866,7 +892,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({
@@ -905,7 +931,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;
@@ -1194,6 +1220,7 @@ module.controller('FeedListCtrl', [
Mousetrap.bind('f', function(e) { Mousetrap.bind('f', function(e) {
$('body').toggleClass('full-screen'); $('body').toggleClass('full-screen');
$('.main-content').css('margin-left', '');
return false; return false;
}); });
@@ -1257,6 +1284,30 @@ module.controller('ManageUsersCtrl', ['$scope', '$state', '$location', 'AdminUse
multiSelect : false, multiSelect : false,
showColumnMenu : true, showColumnMenu : true,
showFilter : true, showFilter : true,
columnDefs : [{
field : 'id',
displayName : 'ID'
}, {
field : 'name',
displayName : 'Name'
}, {
field : 'email',
cellClass : 'E-Mail'
}, {
field : 'created',
cellClass : 'Created',
cellFilter : 'entryDate'
}, {
field : 'lastLogin',
cellClass : 'Last login',
cellFilter : 'entryDate'
}, {
field : 'admin',
cellClass : 'Admin'
}, {
field : 'enabled',
cellClass : 'Enabled'
}],
afterSelectionChange : function(item) { afterSelectionChange : function(item) {
$state.transitionTo('admin.useredit', { $state.transitionTo('admin.useredit', {
@@ -1273,8 +1324,8 @@ module.controller('ManageUsersCtrl', ['$scope', '$state', '$location', 'AdminUse
}; };
}]); }]);
module.controller('ManageUserCtrl', ['$scope', '$state', '$stateParams', '$dialog', 'AdminUsersService', module.controller('ManageUserCtrl', ['$scope', '$state', '$stateParams', 'AdminUsersService',
function($scope, $state, $stateParams, $dialog, AdminUsersService) { function($scope, $state, $stateParams, AdminUsersService) {
$scope.user = $stateParams._id ? AdminUsersService.get({ $scope.user = $stateParams._id ? AdminUsersService.get({
id : $stateParams._id id : $stateParams._id
}) : { }) : {
@@ -1301,76 +1352,14 @@ module.controller('ManageUserCtrl', ['$scope', '$state', '$stateParams', '$dialo
}, alertFunction); }, alertFunction);
}; };
$scope.remove = function() { $scope.remove = function() {
var title = 'Delete user'; AdminUsersService.remove({
var msg = 'Delete user ' + $scope.user.name + ' ?'; id : $scope.user.id
var btns = [{ }, function() {
result : 'cancel', $state.transitionTo('admin.userlist');
label : 'Cancel' }, alertFunction);
}, {
result : 'ok',
label : 'OK',
cssClass : 'btn-primary'
}];
$dialog.messageBox(title, msg, btns).open().then(function(result) {
if (result == 'ok') {
AdminUsersService.remove({
id : $scope.user.id
}, function() {
$state.transitionTo('admin.userlist');
}, alertFunction);
}
});
}; };
}]); }]);
module.controller('ManageDuplicateFeedsCtrl', ['$scope', 'AdminCleanupService', function($scope, AdminCleanupService) {
$scope.limit = 10;
$scope.page = 0;
$scope.minCount = 1;
$scope.mode = 'NORMALIZED_URL';
$scope.mergeData = {};
$scope.refreshData = function() {
AdminCleanupService.findDuplicateFeeds({
mode : $scope.mode,
limit : $scope.limit,
page : $scope.page,
minCount : $scope.minCount
}, function(data) {
$scope.counts = data;
});
};
$scope.autoMerge = function() {
var callback = function() {
alert('done!');
};
for ( var i = 0; i < $scope.counts.length; i++) {
var count = $scope.counts[i];
if (count.autoMerge) {
AdminCleanupService.mergeFeeds({
intoFeedId : count.feeds[0].id,
feedIds : _.pluck(count.feeds, 'id')
}, callback);
}
}
};
$scope.focus = function(count) {
$scope.current = count;
$scope.mergeData.intoFeedId = count.feeds[0].id;
$scope.mergeData.feedIds = _.pluck(count.feeds, 'id');
};
$scope.merge = function() {
AdminCleanupService.mergeFeeds($scope.mergeData, function() {
alert('done!');
});
};
}]);
module.controller('SettingsCtrl', ['$scope', '$location', 'SettingsService', 'AnalyticsService', 'ServerService', module.controller('SettingsCtrl', ['$scope', '$location', 'SettingsService', 'AnalyticsService', 'ServerService',
function($scope, $location, SettingsService, AnalyticsService, ServerService) { function($scope, $location, SettingsService, AnalyticsService, ServerService) {
@@ -1398,8 +1387,8 @@ module.controller('SettingsCtrl', ['$scope', '$location', 'SettingsService', 'An
}; };
}]); }]);
module.controller('ProfileCtrl', ['$scope', '$location', '$dialog', 'ProfileService', 'AnalyticsService', module.controller('ProfileCtrl', ['$scope', '$location', 'ProfileService', 'AnalyticsService',
function($scope, $location, $dialog, ProfileService, AnalyticsService) { function($scope, $location, ProfileService, AnalyticsService) {
AnalyticsService.track(); AnalyticsService.track();
@@ -1423,23 +1412,8 @@ module.controller('ProfileCtrl', ['$scope', '$location', '$dialog', 'ProfileServ
}); });
}; };
$scope.deleteAccount = function() { $scope.deleteAccount = function() {
var title = 'Delete account'; ProfileService.deleteAccount({});
var msg = 'Delete your acount? There\'s no turning back!'; window.location.href = 'logout';
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') {
ProfileService.deleteAccount({});
window.location.href = 'logout';
}
});
}; };
}]); }]);
@@ -1460,9 +1434,6 @@ module.controller('ManageSettingsCtrl', ['$scope', '$location', '$state', 'Admin
$scope.toUsers = function() { $scope.toUsers = function() {
$state.transitionTo('admin.userlist'); $state.transitionTo('admin.userlist');
}; };
$scope.toCleanup = function() {
$state.transitionTo('admin.duplicate_feeds');
};
}]); }]);
module.controller('HelpController', ['$scope', 'CategoryService', 'AnalyticsService', 'ServerService', module.controller('HelpController', ['$scope', 'CategoryService', 'AnalyticsService', 'ServerService',
@@ -1477,11 +1448,13 @@ module.controller('HelpController', ['$scope', 'CategoryService', 'AnalyticsServ
}]); }]);
module.controller('FooterController', ['$scope', function($scope) { module.controller('FooterController', ['$scope', '$sce', function($scope, $sce) {
var baseUrl = window.location.href.substring(0, window.location.href.lastIndexOf('#')); var baseUrl = window.location.href.substring(0, window.location.href.lastIndexOf('#'));
var hostname = window.location.hostname; var hostname = window.location.hostname;
$scope.subToMeUrl = baseUrl + 'rest/feed/subscribe?url={feed}'; var url = baseUrl + 'rest/feed/subscribe?url={feed}';
$scope.subToMeName = hostname.indexOf('www.commafeed.com') !== -1 ? 'CommaFeed' : 'CommaFeed (' + hostname + ')'; var name = hostname.indexOf('www.commafeed.com') !== -1 ? 'CommaFeed' : 'CommaFeed (' + hostname + ')';
var subToMeUrl = 'https://www.subtome.com/register-no-ui.html?name=' + name + '&url=' + url;
$scope.subToMeUrl = $sce.trustAsResourceUrl(subToMeUrl);
}]); }]);

View File

@@ -15,6 +15,22 @@ module.directive('focus', ['$timeout', function($timeout) {
}; };
}]); }]);
module.directive('confirmClick', [function() {
return {
priority : -1,
restrict : 'A',
link : function(scope, element, attrs) {
element.bind('click', function(e) {
var message = attrs.confirmClick;
if (message && !confirm(message)) {
e.stopImmediatePropagation();
e.preventDefault();
}
});
}
};
}]);
/** /**
* Open a popup window pointing to the url in the href attribute * Open a popup window pointing to the url in the href attribute
*/ */
@@ -30,6 +46,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,13 +164,14 @@ module.directive('category', [function() {
selectedId : '=', selectedId : '=',
showLabel : '=', showLabel : '=',
showChildren : '=', showChildren : '=',
unreadCount : '&' unreadCount : '&',
tag : '='
}, },
restrict : 'E', restrict : 'E',
replace : true, replace : true,
templateUrl : 'templates/_category.html', templateUrl : 'templates/_category.html',
controller : ['$scope', '$state', '$dialog', 'FeedService', 'CategoryService', 'SettingsService', 'MobileService', controller : ['$scope', '$state', 'FeedService', 'CategoryService', 'SettingsService', 'MobileService',
function($scope, $state, $dialog, FeedService, CategoryService, SettingsService, MobileService) { function($scope, $state, FeedService, CategoryService, SettingsService, MobileService) {
$scope.settingsService = SettingsService; $scope.settingsService = SettingsService;
$scope.getClass = function(level) { $scope.getClass = function(level) {
@@ -174,13 +223,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 +242,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

@@ -1,5 +1,8 @@
var module = angular.module('commafeed.filters', []); var module = angular.module('commafeed.filters', []);
/**
* smart date formatter
*/
module.filter('entryDate', function() { module.filter('entryDate', function() {
return function(timestamp, defaultValue) { return function(timestamp, defaultValue) {
if (!timestamp) { if (!timestamp) {
@@ -18,10 +21,52 @@ module.filter('entryDate', function() {
}; };
}); });
/**
* rewrites iframes to use https if commafeed uses https
*/
module.filter('iframeHttpsRewrite', function() {
return function(html) {
var result = html;
if (location.protocol === 'https:') {
var wrapper = $('<div></div>').html(html);
$('iframe', wrapper).each(function(i, elem) {
var e = $(elem);
e.attr('src', e.attr('src').replace(/^http:\/\//i, 'https://'));
});
result = wrapper.html();
}
return result;
};
});
/**
* escapes the url
*/
module.filter('escape', function() { module.filter('escape', function() {
return encodeURIComponent; return encodeURIComponent;
}); });
/**
* returns a trusted html content
*/
module.filter('trustHtml', ['$sce', function($sce) {
return function(val) {
return $sce.trustAsHtml(val);
};
}]);
/**
* returns a trusted url
*/
module.filter('trustUrl', ['$sce', function($sce) {
return function(val) {
return $sce.trustAsResourceUrl(val);
};
}]);
/**
* add the 'highlight-search' class to text matching keywords
*/
module.filter('highlight', function() { module.filter('highlight', function() {
return function(html, keywords) { return function(html, keywords) {
if (keywords) { if (keywords) {
@@ -39,7 +84,7 @@ module.filter('highlight', function() {
}; };
var tokens = keywords.split(' '); var tokens = keywords.split(' ');
for ( var i = 0; i < tokens.length; i++) { for (var i = 0; i < tokens.length; i++) {
html = handleKeyword(tokens[i], html); html = handleKeyword(tokens[i], html);
} }
} }

View File

@@ -1,10 +1,10 @@
var app = angular.module('commafeed', ['ui', 'ui.bootstrap', 'ui.state', 'commafeed.directives', 'commafeed.controllers', var app = angular.module('commafeed', ['ngRoute', 'ngTouch', 'ui.utils', 'ui.bootstrap', 'ui.router', 'ui.select2', 'commafeed.directives',
'commafeed.services', 'commafeed.filters', 'ngSanitize', 'infinite-scroll', 'ngGrid']); 'commafeed.controllers', 'commafeed.services', 'commafeed.filters', 'ngSanitize', 'infinite-scroll', 'ngGrid']);
app.config(['$routeProvider', '$stateProvider', '$urlRouterProvider', '$httpProvider', '$compileProvider', app.config(['$routeProvider', '$stateProvider', '$urlRouterProvider', '$httpProvider', '$compileProvider',
function($routeProvider, $stateProvider, $urlRouterProvider, $httpProvider, $compileProvider) { function($routeProvider, $stateProvider, $urlRouterProvider, $httpProvider, $compileProvider) {
$compileProvider.urlSanitizationWhitelist(/^\s*(https?|ftp|mailto|javascript):/); $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|javascript):/);
var interceptor = ['$rootScope', '$q', function(scope, $q) { var interceptor = ['$rootScope', '$q', function(scope, $q) {
var success = function(response) { var success = function(response) {
@@ -39,6 +39,21 @@ app.config(['$routeProvider', '$stateProvider', '$urlRouterProvider', '$httpProv
templateUrl : 'templates/feeds.view.html', templateUrl : 'templates/feeds.view.html',
controller : 'FeedListCtrl' controller : 'FeedListCtrl'
}); });
$stateProvider.state('feeds.subscribe', {
url : '/subscribe',
templateUrl : 'templates/feeds.subscribe.html',
controller : 'SubscribeCtrl'
});
$stateProvider.state('feeds.new_category', {
url : '/add_category',
templateUrl : 'templates/feeds.new_category.html',
controller : 'NewCategoryCtrl'
});
$stateProvider.state('feeds.import', {
url : '/import',
templateUrl : 'templates/feeds.import.html',
controller : 'ImportCtrl'
});
$stateProvider.state('feeds.search', { $stateProvider.state('feeds.search', {
url : '/search/:_keywords', url : '/search/:_keywords',
templateUrl : 'templates/feeds.view.html', templateUrl : 'templates/feeds.view.html',
@@ -54,6 +69,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',
@@ -90,11 +110,6 @@ app.config(['$routeProvider', '$stateProvider', '$urlRouterProvider', '$httpProv
templateUrl : 'templates/admin.useredit.html', templateUrl : 'templates/admin.useredit.html',
controller : 'ManageUserCtrl' controller : 'ManageUserCtrl'
}); });
$stateProvider.state('admin.duplicate_feeds', {
url : '/feeds/duplicates',
templateUrl : 'templates/admin.duplicate_feeds.html',
controller : 'ManageDuplicateFeedsCtrl'
});
$stateProvider.state('admin.settings', { $stateProvider.state('admin.settings', {
url : '/settings', url : '/settings',
templateUrl : 'templates/admin.settings.html', templateUrl : 'templates/admin.settings.html',
@@ -105,7 +120,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

@@ -23,8 +23,7 @@ module.service('MobileService', ['$state', function($state) {
this.rightMenu = !this.rightMenu; this.rightMenu = !this.rightMenu;
$('body').toggleClass('right-menu-active'); $('body').toggleClass('right-menu-active');
}; };
var width = (window.innerWidth > 0) ? window.innerWidth : screen.width; this.mobile = device.mobile() || device.tablet();
this.mobile = width < 979;
}]); }]);
module.factory('ProfileService', ['$resource', function($resource) { module.factory('ProfileService', ['$resource', function($resource) {
@@ -126,7 +125,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 +270,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,27 +315,6 @@ module.factory('AdminMetricsService', ['$resource', function($resource) {
return res; return res;
}]); }]);
module.factory('AdminCleanupService', ['$resource', function($resource) {
var actions = {
findDuplicateFeeds : {
method : 'GET',
isArray : true,
params : {
_method : 'findDuplicateFeeds'
}
},
mergeFeeds : {
method : 'POST',
params : {
_method : 'merge'
}
}
};
var res = $resource('rest/admin/cleanup/:_method', {}, actions);
return res;
}]);
module.factory('ServerService', ['$resource', function($resource) { module.factory('ServerService', ['$resource', function($resource) {
var res = $resource('rest/server/get'); var res = $resource('rest/server/get');
return res; return res;

View File

@@ -1,3 +1,8 @@
.main-content {
margin-left: 250px;
padding-left: 15px;
}
.full-screen .main-content { .full-screen .main-content {
width: 100%; width: 100%;
margin-left: 0; margin-left: 0;
@@ -14,6 +19,7 @@
.entrylist-header h3 { .entrylist-header h3 {
margin: 0; margin: 0;
line-height: 40px;
} }
.entrylist-header a { .entrylist-header a {
@@ -70,7 +76,7 @@
#feed-accordion .entry-heading-link { #feed-accordion .entry-heading-link {
color: black; color: black;
height: 20px; height: 32px;
display: block; display: block;
cursor: pointer; cursor: pointer;
padding: 6px 0px; padding: 6px 0px;
@@ -143,7 +149,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 +178,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

@@ -22,15 +22,20 @@
/* tree*/ /* tree*/
.sidebar-nav-fixed { .sidebar-nav-fixed {
margin-top: 10px; margin-top: 10px;
width: 250px;
position: fixed; position: fixed;
top: 0; top: 0;
bottom: 0; bottom: 0;
width: 16%;
overflow: hidden; overflow: hidden;
} }
.sidebar-nav-fixed:hover { .sidebar-nav-fixed:hover {
overflow-y: auto; overflow-y: auto;
}
.sidebar-nav-fixed .ui-resizable-e {
width: 5px;
right: 0;
} }
.full-screen .left-menu { .full-screen .left-menu {
@@ -39,6 +44,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;
@@ -47,6 +53,7 @@
.css-treeview ul { .css-treeview ul {
list-style: none; list-style: none;
margin-left: 0px; margin-left: 0px;
padding: 0;
} }
.css-treeview li { .css-treeview li {
@@ -118,26 +125,27 @@
.css-treeview .tree-item:hover .config { .css-treeview .tree-item:hover .config {
display: block; display: block;
padding-top: 3px; padding-top: 3px;
margin-right: 5px;
width: 16px; width: 16px;
height: 16px; height: 16px;
} }
.css-treeview .indent1 { .css-treeview .indent1 {
padding-left: 22px; padding-left: 16px;
} }
.css-treeview .indent2 { .css-treeview .indent2 {
padding-left: 44px; padding-left: 32px;
} }
.css-treeview .indent3 { .css-treeview .indent3 {
padding-left: 66px; padding-left: 48px;
} }
.css-treeview .indent4 { .css-treeview .indent4 {
padding-left: 88px; padding-left: 64px;
} }
.css-treeview .indent5 { .css-treeview .indent5 {
padding-left: 110px; padding-left: 80px;
} }

View File

@@ -1,4 +1,3 @@
.toolbar { .toolbar {
padding-top: 10px; padding-top: 10px;
padding-bottom: 10px; padding-bottom: 10px;
@@ -12,3 +11,6 @@
display: inline-block; display: inline-block;
} }
.toolbar .keywords {
width: 244px;
}

View File

@@ -1,7 +1,36 @@
.container-full {
width: 100%;
margin: 0 auto;
padding-right: 20px;
padding-left: 20px;
}
body img {
max-width: 100%;
height: auto;
}
body .modal {
display: block;
}
body .modal-backdrop {
opacity: 0.5;
}
.pointer { .pointer {
cursor: pointer; cursor: pointer;
} }
.nolink {
color: inherit;
}
.nolink:hover {
text-decoration: none;
color: inherit;
}
.block { .block {
display: block; display: block;
} }
@@ -41,6 +70,7 @@
.welcome .preview { .welcome .preview {
margin: 20px 0 20px 0; margin: 20px 0 20px 0;
max-width: 100%;
} }
.welcome .demo { .welcome .demo {

View File

@@ -1,16 +1,25 @@
@media ( max-width : 979px) { html.mobile, html.tablet {
body { body {
padding-left: 0px;
padding-right: 0px;
}
.container-full {
padding-left: 5px; padding-left: 5px;
padding-right: 5px; padding-right: 5px;
} }
.container-fluid { .row {
padding-left: 0; margin-right: 0;
padding-right: 0; margin-left: 0;
} }
.left-menu { .left-menu {
display: none !important; display: none !important;
} }
.toolbar .hidden-desktop.btn-group { .left-menu .feed-link, .left-menu .category-link {
line-height: 30px;
font-size: 105%;
}
.toolbar .toolbar-expand.btn-group {
display: inline-block !important; display: inline-block !important;
} }
.main-content { .main-content {
@@ -18,6 +27,8 @@
float: none !important; float: none !important;
width: 100% !important; width: 100% !important;
margin-left: 0 !important; margin-left: 0 !important;
padding-left: 0;
padding-right: 0;
} }
#feed-accordion .entry-heading .shrink { #feed-accordion .entry-heading .shrink {
margin-left: 0; margin-left: 0;
@@ -30,16 +41,27 @@
margin-top: 22px; margin-top: 22px;
} }
#feed-accordion .entry-external-link { #feed-accordion .entry-external-link {
right: 10px; right: 15px;
margin-top: -24px; margin-top: -24px;
} }
#feed-accordion .entry-heading-link { #feed-accordion .entry-heading-link {
height: 40px; height: 52px;
} }
#feed-accordion .entry-heading .entry-date { #feed-accordion .entry-heading .entry-date {
right: 35px; right: 40px;
margin-top: 22px; margin-top: 22px;
} }
#feed-accordion .entry-buttons .checkbox, #feed-accordion .entry-buttons .form-group {
display: inline-block;
}
#feed-accordion .tags-panel {
display: block;
margin-left: 5px;
}
#feed-accordion a.mark-up-to {
right: 20px;
margin-top: -18px;
}
body.left-menu-active .left-menu { body.left-menu-active .left-menu {
display: block !important; display: block !important;
width: 100%; width: 100%;
@@ -55,10 +77,10 @@
margin-top: 5px; margin-top: 5px;
margin-left: 0; margin-left: 0;
} }
body.right-menu-active .toolbar .actions .visible-desktop { body.right-menu-active .toolbar .actions {
display: inherit !important; display: inherit !important;
} }
#uvTab { #uvTab {
display: none; display: none;
} }
} }

View File

@@ -1,48 +1,37 @@
<li> <li>
<div class="pointer tree-item" ui-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 class="category-link" 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)"
<i ng-class="{'icon-star' : node.id == 'starred', 'icon-inbox': node.id == 'all'}" ng-show="!showChildren"></i> 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 && (node.isTag ? selectedType == 'tag' : selectedType == 'category'))}">
<span ng-class="{unread: unreadCount({category:node})}" class="bidi-embed"> <span ng-class="{unread: unreadCount({category:node})}" class="bidi-embed"> {{categoryLabel(node)}} </span>
{{categoryLabel(node)}} <span class="unread-counter"> {{categoryCountLabel(node)}} </span>
</span>
<span class="unread-counter">
{{categoryCountLabel(node)}}
</span>
</span> </span>
</div> </div>
</div> </div>
<ul ng-show="node.expanded && showChildren"> <ul ng-show="node.expanded && showChildren">
<recursive> <recursive> <category ng-repeat="child in node.children"
<category ng-repeat="child in node.children" ng-show="settingsService.settings.showRead == true || unreadCount({category:node}) > 0" ng-show="settingsService.settings.showRead == true || unreadCount({category:node}) > 0" node="child" level="level + 1"
node="child" level="level + 1" selected-type="selectedType" selected-type="selectedType" selected-id="selectedId" show-label="true" show-children="true"
selected-id="selectedId" show-label="true" show-children="true" unread-count="unreadCount({category:category})"> </category> </recursive>
unread-count="unreadCount({category:category})">
</category>
</recursive>
<li ng-repeat="feed in node.feeds" ng-class="getClass(level)" class="tree-item" draggable="feed" droppable="feed" <li ng-repeat="feed in node.feeds" ng-class="getClass(level)" class="tree-item" draggable="feed" droppable="feed"
ng-show="settingsService.settings.showRead == true || feed.unread > 0"> ng-show="settingsService.settings.showRead == true || feed.unread > 0">
<div class="pull-right" ng-click="showFeedDetails(feed)"> <div class="pull-right" ng-click="showFeedDetails(feed)">
<i class="icon-wrench config pointer"></i> <i class="icon-wrench config pointer"></i>
</div> </div>
<a ng-click="feedClicked(feed.id, $event)" ng-dblclick="showFeedDetails(feed)" class="feed-link" <a ng-click="feedClicked(feed.id, $event)" ng-dblclick="showFeedDetails(feed)" class="feed-link" href="{{feed.feedLink}}" target="_blank"
href="{{feed.feedLink}}" target="_blank"
ng-class="{error: feed.message && feed.errorCount > 10, selected: (feed.id == selectedId && selectedType == 'feed') }"> ng-class="{error: feed.message && feed.errorCount > 10, selected: (feed.id == selectedId && selectedType == 'feed') }">
<favicon url="feed.iconUrl" /> <favicon url="feed.iconUrl" />
<span ng-class="{unread: feed.unread}" class="bidi-embed"> <span ng-class="{unread: feed.unread}" class="bidi-embed"> {{feed.name}} </span>
{{feed.name}} <span class="unread-counter"> {{feedCountLabel(feed)}} </span>
</span>
<span class="unread-counter">
{{feedCountLabel(feed)}}
</span>
</a> </a>
</li> </li>
</ul> </ul>

View File

@@ -1,27 +1,29 @@
<div ng-controller="FeedSearchCtrl"> <div ng-controller="FeedSearchCtrl">
<div modal="feedSearchModal" close="close()" <div modal="feedSearchModal" close="close()" options="{dialogClass: 'modal feed-search-dialog'}">
options="{dialogClass: 'modal feed-search-dialog'}"> <div class="modal-dialog">
<div class="modal-header"> <div class="modal-content">
<button type="button" class="close" ng-click="close()">&times;</button> <div class="modal-header">
<h4> <button type="button" class="close" ng-click="close()">&times;</button>
<input ng-model="filter" class="filter-input" <h4>
ui-keydown="{'up': 'focusPrevious($event)', 'down': 'focusNext($event)', 'enter': 'openFocused()' }" <input ng-model="filter" class="filter-input"
placeholder="${feedsearch.hint}" focus="feedSearchModal"> ui-keydown="{'up': 'focusPrevious($event)', 'down': 'focusNext($event)', 'enter': 'openFocused()' }" placeholder="${feedsearch.hint}"
</h4> focus="feedSearchModal">
<small>${feedsearch.help}</small> </h4>
</div> <small>${feedsearch.help}</small>
<div class="modal-body"> </div>
<strong>${feedsearch.result_prefix}</strong> <div class="modal-body">
<span <strong>${feedsearch.result_prefix}</strong>
ng-repeat="feed in (filtered = (CategoryService.feeds | filter:{name: filter} | limitTo:40))"> <span ng-repeat="feed in (filtered = (CategoryService.feeds | filter:{name: filter} | limitTo:40))">
<span ng-class="{block: filter, focus: focus.id == feed.id}" class="feed-link"> <span ng-class="{block: filter, focus: focus.id == feed.id}" class="feed-link">
<a class=" pointer" ng-click="goToFeed(feed.id)"> <a class=" pointer" ng-click="goToFeed(feed.id)">
<favicon url="feed.iconUrl" /> <favicon url="feed.iconUrl" />
&nbsp;{{feed.name}} &nbsp;{{feed.name}}
</a> </a>
<span ng-show="!filter && !$last">&bull;</span> <span ng-show="!filter && !$last">&bull;</span>
</span> </span>
</span> </span>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,3 +1,3 @@
<div ng-controller="FooterController"> <div ng-controller="FooterController">
<iframe ui-if="subToMeUrl" style="display:none;" ng-src='https://www.subtome.com/register-no-ui.html?name={{subToMeName}}&url={{subToMeUrl}}'></iframe> <iframe ng-if="subToMeUrl" style="display: none;" ng-src='{{subToMeUrl}}'></iframe>
</div> </div>

Some files were not shown because too many files have changed in this diff Show More