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>
<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>
<protocol type="TCPPING">
<property name="timeout">3000</property>
@@ -476,15 +476,15 @@
<interfaces>
<interface name="management">
<loopback-address value="${env.OPENSHIFT_INTERNAL_IP}" />
<loopback-address value="${env.OPENSHIFT_JBOSSEAP_IP}" />
</interface>
<interface name="public">
<loopback-address value="${env.OPENSHIFT_INTERNAL_IP}" />
<loopback-address value="${env.OPENSHIFT_JBOSSEAP_IP}" />
</interface>
<interface name="unsecure">
<!-- Used for IIOP sockets in the standarad configuration. To secure JacORB
you need to setup SSL -->
<loopback-address value="${env.OPENSHIFT_INTERNAL_IP}" />
<loopback-address value="${env.OPENSHIFT_JBOSSEAP_IP}" />
</interface>
</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 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
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
Embedded HSQL database:
# Embedded HSQL database:
mvn clean package tomee:build -Pprod
External MySQL database:
# External MySQL database:
mvn clean package tomee:build -Pprod -Pmysql
External PostgreSQL database:
# External PostgreSQL database:
mvn clean package tomee:build -Pprod -Ppgsql
External Microsoft SQL Server database:
# External Microsoft SQL Server database:
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.
@@ -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 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
-----------------

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>
<artifactId>commafeed</artifactId>
<version>1.3.0</version>
<version>1.4.0</version>
<packaging>war</packaging>
<name>CommaFeed</name>
@@ -79,7 +79,7 @@
<artifactId>tomee-maven-plugin</artifactId>
<version>1.5.2</version>
<configuration>
<tomeeVersion>1.5.2</tomeeVersion>
<tomeeVersion>1.6.0</tomeeVersion>
<tomeeClassifier>plus</tomeeClassifier>
<tomeeHttpPort>8082</tomeeHttpPort>
<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:hibernate-validator:4.3.1.Final</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>antlr:antlr:2.7.7</lib>
<lib>remove:openjpa-</lib>
<lib>remove:hsqldb</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>net.sourceforge.jtds:jtds:1.3.1</lib>
@@ -169,7 +174,7 @@
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
<version>2.1.5</version>
<version>2.1.7</version>
<executions>
<execution>
<goals>
@@ -189,7 +194,7 @@
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>0.12.0</version>
<version>1.12.4</version>
<scope>provided</scope>
</dependency>
<dependency>
@@ -214,28 +219,28 @@
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.1.0</version>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<version>3.0.2</version>
<version>3.0.7</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>14.0.1</version>
<version>16.0.1</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.8.3</version>
<version>1.9.1</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.8</version>
<version>1.9</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
@@ -260,7 +265,7 @@
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3</version>
<version>1.3.1</version>
</dependency>
<dependency>
@@ -298,34 +303,34 @@
<dependency>
<groupId>com.google.gwt</groupId>
<artifactId>gwt-servlet</artifactId>
<version>2.5.1</version>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>net.sourceforge.cssparser</groupId>
<artifactId>cssparser</artifactId>
<version>0.9.9</version>
<version>0.9.13</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.2.5</version>
<version>4.3.2</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.7.2</version>
<version>1.7.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.2.2</version>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
<version>1.7.6</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
@@ -336,23 +341,23 @@
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket-core</artifactId>
<version>6.10.0</version>
<version>6.13.0</version>
</dependency>
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket-auth-roles</artifactId>
<version>6.10.0</version>
<version>6.13.0</version>
</dependency>
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket-extensions</artifactId>
<version>6.10.0</version>
<version>6.13.0</version>
</dependency>
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket-cdi</artifactId>
<version>6.10.0</version>
<version>6.13.0</version>
</dependency>
<dependency>
<groupId>ro.isdc.wro4j</groupId>
@@ -549,7 +554,7 @@
<plugin>
<groupId>ro.isdc.wro4j</groupId>
<artifactId>wro4j-maven-plugin</artifactId>
<version>1.6.3</version>
<version>1.7.2</version>
<executions>
<execution>
<goals>

View File

@@ -7,44 +7,33 @@ import java.security.cert.X509Certificate;
import javax.net.ssl.KeyManager;
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.X509TrustManager;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.http.Consts;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
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.HttpUriRequest;
import org.apache.http.client.params.CookiePolicy;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
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.client.protocol.HttpClientContext;
import org.apache.http.config.ConnectionConfig;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.wicket.util.io.IOUtils;
/**
* 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 PRAGMA_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;
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 {
return getBinary(url, null, null, timeout);
}
@@ -95,10 +80,11 @@ public class HttpGetter {
HttpResult result = null;
long start = System.currentTimeMillis();
HttpClient client = newClient(timeout);
CloseableHttpClient client = newClient(timeout);
CloseableHttpResponse response = null;
try {
HttpGet httpget = new HttpGet(url);
HttpContext context = new BasicHttpContext();
HttpClientContext context = HttpClientContext.create();
httpget.addHeader(HttpHeaders.ACCEPT_LANGUAGE, ACCEPT_LANGUAGE);
httpget.addHeader(HttpHeaders.PRAGMA, PRAGMA_NO_CACHE);
@@ -112,7 +98,6 @@ public class HttpGetter {
httpget.addHeader(HttpHeaders.IF_NONE_MATCH, eTag);
}
HttpResponse response = null;
try {
response = client.execute(httpget, context);
int code = response.getStatusLine().getStatusCode();
@@ -150,14 +135,15 @@ public class HttpGetter {
contentType = entity.getContentType().getValue();
}
}
HttpUriRequest req = (HttpUriRequest) context.getAttribute(ExecutionContext.HTTP_REQUEST);
HttpHost host = (HttpHost) context.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
HttpUriRequest req = (HttpUriRequest) context.getRequest();
HttpHost host = context.getTargetHost();
String urlAfterRedirect = req.getURI().isAbsolute() ? req.getURI().toString() : host.toURI() + req.getURI();
long duration = System.currentTimeMillis() - start;
result = new HttpResult(content, contentType, lastModifiedHeaderValue, eTagHeaderValue, duration, urlAfterRedirect);
} finally {
client.getConnectionManager().shutdown();
IOUtils.closeQuietly(response);
IOUtils.closeQuietly(client);
}
return result;
}
@@ -205,21 +191,24 @@ public class HttpGetter {
}
}
public static HttpClient newClient(int timeout) {
DefaultHttpClient client = new SystemDefaultHttpClient();
public static CloseableHttpClient newClient(int timeout) {
HttpClientBuilder builder = HttpClients.custom();
builder.useSystemProperties();
builder.disableAutomaticRetries();
SSLSocketFactory ssf = new SSLSocketFactory(SSL_CONTEXT, VERIFIER);
ClientConnectionManager ccm = client.getConnectionManager();
SchemeRegistry sr = ccm.getSchemeRegistry();
sr.register(new Scheme(HTTPS, 443, ssf));
builder.setSslcontext(SSL_CONTEXT);
builder.setHostnameVerifier(SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
HttpParams params = client.getParams();
HttpClientParams.setCookiePolicy(params, CookiePolicy.IGNORE_COOKIES);
HttpProtocolParams.setContentCharset(params, UTF8);
HttpConnectionParams.setConnectionTimeout(params, timeout);
HttpConnectionParams.setSoTimeout(params, timeout);
client.setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler(0, false));
return new DecompressingHttpClient(client);
RequestConfig.Builder configBuilder = RequestConfig.custom();
configBuilder.setCookieSpec(CookieSpecs.IGNORE_COOKIES);
configBuilder.setSocketTimeout(timeout);
configBuilder.setConnectTimeout(timeout);
configBuilder.setConnectionRequestTimeout(timeout);
builder.setDefaultRequestConfig(configBuilder.build());
builder.setDefaultConnectionConfig(ConnectionConfig.custom().setCharset(Consts.UTF_8).build());
return builder.build();
}
public static class NotModifiedException extends Exception {
@@ -245,24 +234,4 @@ public class HttpGetter {
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;
/**
* 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() {
Date threshold = applicationSettingsService.getUnreadThreshold();
if (threshold != null) {
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.concurrent.TimeUnit;
import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Alternative;
import org.apache.commons.pool.impl.GenericObjectPool;
import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
@@ -30,7 +33,14 @@ public class RedisCacheService extends CacheService {
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
public List<String> getLastEntries(Feed feed) {

View File

@@ -6,15 +6,12 @@ import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.SetJoin;
import javax.persistence.criteria.Subquery;
import javax.persistence.metamodel.SingularAttribute;
import org.apache.commons.codec.digest.DigestUtils;
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.User;
import com.commafeed.backend.model.User_;
import com.commafeed.frontend.model.FeedCount;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
@@ -95,8 +91,8 @@ public class FeedDAO extends GenericDAO<Feed> {
public List<Feed> findByTopic(String 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());
Root<Feed> root = query.from(getType());
@@ -105,54 +101,6 @@ public class FeedDAO extends GenericDAO<Feed> {
TypedQuery<Feed> q = em.createQuery(query);
q.setMaxResults(max);
List<Feed> list = 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;
return q.getResultList();
}
}

View File

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

View File

@@ -11,6 +11,7 @@ import javax.persistence.criteria.Root;
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.Feed_;
@@ -36,6 +37,21 @@ public class FeedEntryDAO extends GenericDAO<FeedEntry> {
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) {
CriteriaQuery<FeedEntry> query = builder.createQuery(getType());
Root<FeedEntry> root = query.from(getType());

View File

@@ -15,8 +15,9 @@ import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
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.criterion.Conjunction;
import org.hibernate.criterion.Disjunction;
import org.hibernate.criterion.MatchMode;
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.FeedEntryStatus;
import com.commafeed.backend.model.FeedEntryStatus_;
import com.commafeed.backend.model.FeedEntryTag;
import com.commafeed.backend.model.FeedEntryTag_;
import com.commafeed.backend.model.FeedEntry_;
import com.commafeed.backend.model.FeedSubscription;
import com.commafeed.backend.model.Models;
@@ -40,31 +43,34 @@ import com.commafeed.backend.services.ApplicationSettingsService;
import com.commafeed.frontend.model.UnreadCount;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
@Stateless
public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
private static final String ALIAS_STATUS = "status";
private static final String ALIAS_ENTRY = "entry";
private static final String ALIAS_TAG = "tag";
@Inject
FeedEntryTagDAO feedEntryTagDAO;
private static final Comparator<FeedEntryStatus> STATUS_COMPARATOR_DESC = new Comparator<FeedEntryStatus>() {
@Override
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>() {
@Override
public int compare(FeedEntryStatus o1, FeedEntryStatus o2) {
return ObjectUtils.compare(o1.getEntryUpdated(), o2.getEntryUpdated());
};
};
private static final Comparator<FeedEntryStatus> STATUS_COMPARATOR_ASC = Ordering.from(STATUS_COMPARATOR_DESC).reverse();
@Inject
ApplicationSettingsService applicationSettingsService;
public FeedEntryStatus getStatus(FeedSubscription sub, FeedEntry entry) {
public FeedEntryStatus getStatus(User user, FeedSubscription sub, FeedEntry entry) {
CriteriaQuery<FeedEntryStatus> query = builder.createQuery(getType());
Root<FeedEntryStatus> root = query.from(getType());
@@ -77,14 +83,14 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
List<FeedEntryStatus> statuses = em.createQuery(query).getResultList();
FeedEntryStatus status = Iterables.getFirst(statuses, null);
return handleStatus(status, sub, entry);
return handleStatus(user, status, sub, entry);
}
private FeedEntryStatus handleStatus(FeedEntryStatus status, FeedSubscription sub, FeedEntry entry) {
private FeedEntryStatus handleStatus(User user, FeedEntryStatus status, FeedSubscription sub, FeedEntry entry) {
if (status == null) {
Date unreadThreshold = applicationSettingsService.getUnreadThreshold();
boolean read = unreadThreshold == null ? false : entry.getUpdated().before(unreadThreshold);
status = new FeedEntryStatus(sub.getUser(), sub, entry);
status = new FeedEntryStatus(user, sub, entry);
status.setRead(read);
status.setMarkable(!read);
} else {
@@ -93,6 +99,12 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
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) {
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_.starred), true));
query.where(predicates.toArray(new Predicate[0]));
if (newerThan != null) {
predicates.add(builder.greaterThanOrEqualTo(root.get(FeedEntryStatus_.entryInserted), newerThan));
}
query.where(predicates.toArray(new Predicate[0]));
orderStatusesBy(query, root, order);
@@ -115,13 +128,14 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
setTimeout(q);
List<FeedEntryStatus> statuses = q.getResultList();
for (FeedEntryStatus status : statuses) {
status = handleStatus(status, status.getSubscription(), status.getEntry());
status = handleStatus(user, status, status.getSubscription(), status.getEntry());
status = fetchTags(user, status);
}
return lazyLoadContent(includeContent, statuses);
}
private Criteria buildSearchCriteria(FeedSubscription sub, boolean unreadOnly, String keywords, Date newerThan, int offset, int limit,
ReadingOrder order, Date last) {
private Criteria buildSearchCriteria(User user, FeedSubscription sub, boolean unreadOnly, String keywords, Date newerThan, int offset, int limit,
ReadingOrder order, Date last, String tag) {
Criteria criteria = getSession().createCriteria(FeedEntry.class, ALIAS_ENTRY);
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,
Restrictions.eq(FeedEntryStatus_.subscription.getName(), sub));
if (unreadOnly) {
if (unreadOnly && tag == null) {
Disjunction or = Restrictions.disjunction();
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) {
criteria.add(Restrictions.ge(FeedEntry_.inserted.getName(), newerThan));
}
@@ -165,13 +186,11 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
}
if (order != null) {
Order o = null;
if (order == ReadingOrder.asc) {
o = Order.asc(FeedEntry_.updated.getName());
criteria.addOrder(Order.asc(FeedEntry_.updated.getName())).addOrder(Order.asc(FeedEntry_.id.getName()));
} 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) {
criteria.setFirstResult(offset);
@@ -188,14 +207,14 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
}
@SuppressWarnings("unchecked")
public List<FeedEntryStatus> findBySubscriptions(List<FeedSubscription> subs, boolean unreadOnly, String keywords, Date newerThan,
int offset, int limit, ReadingOrder order, boolean includeContent, boolean onlyIds) {
public List<FeedEntryStatus> findBySubscriptions(User user, List<FeedSubscription> subs, boolean unreadOnly, String keywords,
Date newerThan, int offset, int limit, ReadingOrder order, boolean includeContent, boolean onlyIds, String tag) {
int capacity = offset + limit;
Comparator<FeedEntryStatus> comparator = order == ReadingOrder.desc ? STATUS_COMPARATOR_DESC : STATUS_COMPARATOR_ASC;
FixedSizeSortedSet<FeedEntryStatus> set = new FixedSizeSortedSet<FeedEntryStatus>(capacity, comparator);
for (FeedSubscription sub : subs) {
Date last = (order != null && set.isFull()) ? set.last().getEntryUpdated() : null;
Criteria criteria = buildSearchCriteria(sub, unreadOnly, keywords, newerThan, -1, capacity, order, last);
Criteria criteria = buildSearchCriteria(user, sub, unreadOnly, keywords, newerThan, -1, capacity, order, last, tag);
ProjectionList projection = Projections.projectionList();
projection.add(Projections.property("id"), "id");
projection.add(Projections.property("updated"), "updated");
@@ -237,7 +256,10 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
for (FeedEntryStatus placeholder : placeholders) {
Long statusId = placeholder.getId();
FeedEntry entry = em.find(FeedEntry.class, placeholder.getEntry().getId());
statuses.add(handleStatus(statusId == null ? null : findById(statusId), placeholder.getSubscription(), entry));
FeedEntryStatus status = handleStatus(user, statusId == null ? null : findById(statusId), placeholder.getSubscription(),
entry);
status = fetchTags(user, status);
statuses.add(status);
}
statuses = lazyLoadContent(includeContent, statuses);
}
@@ -245,9 +267,9 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
}
@SuppressWarnings("unchecked")
public UnreadCount getUnreadCount(FeedSubscription subscription) {
public UnreadCount getUnreadCount(User user, FeedSubscription subscription) {
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();
projection.add(Projections.rowCount(), "count");
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) {
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 == ReadingOrder.asc) {
query.orderBy(builder.asc(date));
query.orderBy(builder.asc(date), builder.asc(id));
} 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) {
delete(object);
}
return objects.size();
}
public void deleteById(Long id) {

View File

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

View File

@@ -82,7 +82,7 @@ public class FeedParser {
continue;
}
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));
FeedEntryContent content = new FeedEntryContent();

View File

@@ -8,11 +8,11 @@ import com.commafeed.backend.model.FeedEntry;
public class FeedRefreshContext {
private Feed feed;
private List<FeedEntry> entries;
private boolean isUrgent;
private boolean urgent;
public FeedRefreshContext(Feed feed, boolean isUrgent) {
this.feed = feed;
this.isUrgent = isUrgent;
this.urgent = isUrgent;
}
public Feed getFeed() {
@@ -24,11 +24,11 @@ public class FeedRefreshContext {
}
public boolean isUrgent() {
return isUrgent;
return urgent;
}
public void setUrgent(boolean isUrgent) {
this.isUrgent = isUrgent;
public void setUrgent(boolean urgent) {
this.urgent = urgent;
}
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.lang.time.DateUtils;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
import com.commafeed.backend.dao.FeedDAO;
@@ -66,6 +67,24 @@ public class FeedRefreshTaskGiver {
feedRefreshed = metrics.meter(MetricRegistry.name(getClass(), "feedRefreshed"));
threadWaited = metrics.meter(MetricRegistry.name(getClass(), "threadWaited"));
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
@@ -147,22 +166,25 @@ public class FeedRefreshTaskGiver {
*/
private void refill() {
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();
if (!applicationSettingsService.get().isCrawlingPaused()) {
List<Feed> feeds = feedDAO.findNextUpdatable(count, getLastLoginThreshold());
for (Feed feed : feeds) {
contexts.add(new FeedRefreshContext(feed, false));
}
int batchSize = Math.min(100, 3 * backgroundThreads);
// add feeds we got from the add() method
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
// higher priority
int size = addQueue.size();
for (int i = 0; i < size; i++) {
contexts.add(0, addQueue.poll());
// add feeds that are up to refresh from the database
if (!applicationSettingsService.get().isCrawlingPaused()) {
int count = batchSize - contexts.size();
if (count > 0) {
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
@@ -178,8 +200,8 @@ public class FeedRefreshTaskGiver {
takeQueue.addAll(map.values());
// add feeds from the giveBack queue to the map, overriding duplicates
size = giveBackQueue.size();
for (int i = 0; i < size; i++) {
int giveBackQueueSize = giveBackQueue.size();
for (int i = 0; i < giveBackQueueSize; i++) {
Feed feed = giveBackQueue.poll();
map.put(feed.getId(), new FeedRefreshContext(feed, false));
}

View File

@@ -1,6 +1,8 @@
package com.commafeed.backend.feeds;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
@@ -15,6 +17,7 @@ import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.math.stat.descriptive.SummaryStatistics;
import org.apache.wicket.request.UrlUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Document.OutputSettings;
@@ -389,17 +392,37 @@ public class FeedUtils {
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));
if (baseUrl == null || url == null || url.startsWith("http")) {
if (url == null || url.startsWith("http")) {
return url;
}
if (url.startsWith("/") == false) {
url = "/" + url;
String baseUrl = (feedLink == null || UrlUtils.isRelative(feedLink)) ? feedUrl : feedLink;
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) {

View File

@@ -55,5 +55,8 @@ public class FeedEntry extends AbstractModel {
@OneToMany(mappedBy = "entry", cascade = CascadeType.REMOVE)
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;
import java.util.Date;
import java.util.List;
import javax.persistence.Cacheable;
import javax.persistence.Column;
@@ -8,6 +9,8 @@ import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
@@ -19,6 +22,8 @@ import lombok.Setter;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import com.google.common.collect.Lists;
@Entity
@Table(name = "FEEDENTRYSTATUSES")
@SuppressWarnings("serial")
@@ -26,6 +31,7 @@ import org.hibernate.annotations.CacheConcurrencyStrategy;
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
@Getter
@Setter
@NamedQueries(@NamedQuery(name="Statuses.deleteOld", query="delete from FeedEntryStatus s where s.entryInserted < :date and s.starred = false"))
public class FeedEntryStatus extends AbstractModel {
@ManyToOne(fetch = FetchType.LAZY)
@@ -42,6 +48,9 @@ public class FeedEntryStatus extends AbstractModel {
@Transient
private boolean markable;
@Transient
private List<FeedEntryTag> tags = Lists.newArrayList();
/**
* 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)
private String name;
@Column(length = 255, unique = true)
private String email;
@@ -66,4 +66,8 @@ public class User extends AbstractModel {
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
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)
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.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
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.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.apache.wicket.util.io.IOUtils;
import com.commafeed.backend.HttpGetter;
import com.commafeed.backend.feeds.FeedRefreshTaskGiver;
@@ -67,10 +68,11 @@ public class SubscriptionHandler {
post.setHeader(HttpHeaders.USER_AGENT, "CommaFeed");
post.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED);
HttpClient client = HttpGetter.newClient(20000);
CloseableHttpClient client = HttpGetter.newClient(20000);
CloseableHttpResponse response = null;
try {
post.setEntity(new UrlEncodedFormEntity(nvp));
HttpResponse response = client.execute(post);
response = client.execute(post);
int code = response.getStatusLine().getStatusCode();
if (code != 204 && code != 202 && code != 200) {
@@ -90,7 +92,8 @@ public class SubscriptionHandler {
} catch (Exception e) {
log.error("Could not subscribe to {} for {} : " + e.getMessage(), hub, topic);
} finally {
client.getConnectionManager().shutdown();
IOUtils.closeQuietly(response);
IOUtils.closeQuietly(client);
}
}
}

View File

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

View File

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

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);
if (count == null) {
log.debug("unread count cache miss for {}", Models.getId(sub));
count = feedEntryStatusDAO.getUnreadCount(sub);
count = feedEntryStatusDAO.getUnreadCount(user, sub);
cache.setUnreadCount(sub, count);
}
return count;
@@ -109,7 +117,7 @@ public class FeedSubscriptionService {
Map<Long, UnreadCount> map = Maps.newHashMap();
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user);
for (FeedSubscription sub : subs) {
map.put(sub.getId(), getUnreadCount(sub));
map.put(sub.getId(), getUnreadCount(user, sub));
}
return map;
}

View File

@@ -41,6 +41,9 @@ public class UserService {
@Inject
ApplicationSettingsService applicationSettingsService;
@Inject
FeedSubscriptionService feedSubscriptionService;
public User login(String name, String password) {
if (name == null || password == null) {
return null;
@@ -52,10 +55,21 @@ public class UserService {
if (authenticated) {
Date lastLogin = user.getLastLogin();
Date now = new Date();
boolean saveUser = false;
// only update lastLogin field every hour in order to not
// invalidate the cache everytime someone logs in
if (lastLogin == null || lastLogin.before(DateUtils.addHours(now, -1))) {
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);
}
return user;

View File

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

View File

@@ -38,5 +38,8 @@ public class Settings implements Serializable {
@ApiProperty(value = "user's custom css for the website")
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>
<html xmlns:wicket="http://wicket.apache.org" wicket:id="html">
<head>
<title>CommaFeed</title>
<meta charset="utf-8">
<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="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="114x114" href="app-icon-114.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="64x64" href="app-icon-64.png" />
<link rel="icon" sizes="128x128" href="app-icon-128.png" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
<meta name="application-name" content="CommaFeed" />
<meta name="msapplication-navbutton-color" content="#F88A14" />
<meta name="msapplication-starturl" content="/" />
<meta name="msapplication-square70x70logo" content="metro-icon-70.png"/>
<meta name="msapplication-square150x150logo" content="metro-icon-150.png"/>
<link rel="fluid-icon" href="app-icon-512.png" title="CommaFeed" />
<link rel="logo" type="image/svg" href="app-icon.svg" />
<title>CommaFeed</title>
<meta charset="utf-8">
<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="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="114x114" href="app-icon-114.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="64x64" href="app-icon-64.png" />
<link rel="icon" sizes="128x128" href="app-icon-128.png" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
<meta name="application-name" content="CommaFeed" />
<meta name="msapplication-navbutton-color" content="#F88A14" />
<meta name="msapplication-starturl" content="/" />
<meta name="msapplication-square70x70logo" content="metro-icon-70.png" />
<meta name="msapplication-square150x150logo" content="metro-icon-150.png" />
<link rel="fluid-icon" href="app-icon-512.png" title="CommaFeed" />
<link rel="logo" type="image/svg" href="app-icon.svg" />
</head>
<body>
<wicket:child />
<wicket:container wicket:id="footer-container"/>
<wicket:container wicket:id="footer-container" />
<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>
UserVoice = window.UserVoice || [];
UserVoice.push(['showTab', 'classic_widget', {
mode: 'full',
default_mode: 'feedback',
primary_color: '#000',
link_color: '#007dbf',
forum_id: 204509,
support_tab_name: 'Contact',
feedback_tab_name: 'Feedback',
tab_label: 'Feedback',
tab_color: '#7e72db',
tab_position: 'bottom-right',
tab_inverted: false
mode : 'full',
default_mode : 'feedback',
primary_color : '#000',
link_color : '#007dbf',
forum_id : 204509,
support_tab_name : 'Contact',
feedback_tab_name : 'Feedback',
tab_label : 'Feedback',
tab_color : '#7e72db',
tab_position : 'bottom-right',
tab_inverted : false
}]);
</script>
</wicket:container>

View File

@@ -54,13 +54,13 @@ public class NextUnreadRedirectPage extends WebPage {
List<FeedEntryStatus> statuses = null;
if (StringUtils.isBlank(categoryId) || CategoryREST.ALL.equals(categoryId)) {
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 {
FeedCategory category = feedCategoryDAO.findById(user, Long.valueOf(categoryId));
if (category != null) {
List<FeedCategory> children = feedCategoryDAO.findAllChildrenCategories(user, category);
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">
<img src="images/logo_2.png" />
<div wicket:id="feedback"></div>
<form wicket:id="form">
New Password:
<input type="password" wicket:id="password" />
<br />
Confirm:
<input type="password" wicket:id="confirm" />
<br />
<input type="submit" class="btn btn-primary" value="Submit" />
<input type="button" class="btn" wicket:id="cancel" value="Home page" />
<form wicket:id="form" class="form-horizontal">
<div class="form-group">
<label>New Password</label>
<input type="password" wicket:id="password" />
</div>
<div class="form-group">
<label>Confirm</label>
<input type="password" wicket:id="confirm" />
</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>
</div>
</div>

View File

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

View File

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

View File

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

View File

@@ -4,23 +4,17 @@
<wicket:panel>
<div wicket:id="feedback"></div>
<form wicket:id="form" autocomplete="off">
<div class="control-group">
<label class="control-label">User Name</label>
<div class="controls">
<input type="text" wicket:id="name" class="input-block-level"></input>
</div>
<div class="form-group">
<label>User Name</label>
<input type="text" wicket:id="name" class="form-control"></input>
</div>
<div class="control-group">
<label class="control-label">Password</label>
<div class="controls">
<input type="password" wicket:id="password" class="input-block-level"></input>
</div>
<div class="form-group">
<label>Password</label>
<input type="password" wicket:id="password" class="form-control"></input>
</div>
<div class="control-group">
<label class="control-label">Email address (used for password recovery only)</label>
<div class="controls">
<input type="email" wicket:id="email" class="input-block-level"></input>
</div>
<div class="form-group">
<label>Email address (used for password recovery only)</label>
<input type="email" wicket:id="email" class="form-control"></input>
</div>
<div>
<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()));
return authorized;
}
}

View File

@@ -1,6 +1,5 @@
package com.commafeed.frontend.rest.resources;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@@ -19,14 +18,12 @@ import org.apache.commons.lang.StringUtils;
import com.codahale.metrics.MetricRegistry;
import com.commafeed.backend.dao.FeedDAO;
import com.commafeed.backend.dao.FeedDAO.DuplicateMode;
import com.commafeed.backend.dao.UserDAO;
import com.commafeed.backend.dao.UserRoleDAO;
import com.commafeed.backend.feeds.FeedRefreshTaskGiver;
import com.commafeed.backend.feeds.FeedRefreshUpdater;
import com.commafeed.backend.feeds.FeedRefreshWorker;
import com.commafeed.backend.model.ApplicationSettings;
import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.User;
import com.commafeed.backend.model.UserRole;
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.startup.StartupBean;
import com.commafeed.frontend.SecurityCheck;
import com.commafeed.frontend.model.FeedCount;
import com.commafeed.frontend.model.UserModel;
import com.commafeed.frontend.model.request.FeedMergeRequest;
import com.commafeed.frontend.model.request.IDRequest;
import com.commafeed.frontend.rest.PrettyPrint;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.wordnik.swagger.annotations.Api;
@@ -259,36 +253,4 @@ public class AdminREST extends AbstractREST {
map.put("old_entries", cleaner.cleanEntriesOlderThan(days, TimeUnit.DAYS));
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 org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.ObjectUtils;
import com.commafeed.backend.cache.CacheService;
import com.commafeed.backend.dao.FeedCategoryDAO;
@@ -106,7 +106,8 @@ public class CategoryREST extends AbstractREST {
@ApiParam(
value = "search for keywords in either the title or the content of the entries, separated by spaces, 3 characters minimum") @QueryParam("keywords") String keywords,
@ApiParam(value = "return only entry ids") @DefaultValue("false") @QueryParam("onlyIds") boolean onlyIds,
@ApiParam(value = "comma-separated list of excluded subscription ids") @QueryParam("excludedSubscriptionIds") String excludedSubscriptionIds) {
@ApiParam(value = "comma-separated list of excluded subscription ids") @QueryParam("excludedSubscriptionIds") String excludedSubscriptionIds,
@ApiParam(value = "keep only entries tagged with this tag") @QueryParam("tag") String tag) {
Preconditions.checkNotNull(readType);
@@ -135,11 +136,11 @@ public class CategoryREST extends AbstractREST {
}
if (ALL.equals(id)) {
entries.setName("All");
entries.setName(ObjectUtils.defaultIfNull(tag, "All"));
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(getUser());
removeExcludedSubscriptions(subs, excludedIds);
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(subs, unreadOnly, keywords, newerThanDate, offset,
limit + 1, order, true, onlyIds);
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(getUser(), subs, unreadOnly, keywords, newerThanDate,
offset, limit + 1, order, true, onlyIds, tag);
for (FeedEntryStatus status : list) {
entries.getEntries().add(
@@ -161,8 +162,8 @@ public class CategoryREST extends AbstractREST {
List<FeedCategory> categories = feedCategoryDAO.findAllChildrenCategories(getUser(), parent);
List<FeedSubscription> subs = feedSubscriptionDAO.findByCategories(getUser(), categories);
removeExcludedSubscriptions(subs, excludedIds);
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(subs, unreadOnly, keywords, newerThanDate, offset,
limit + 1, order, true, onlyIds);
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(getUser(), subs, unreadOnly, keywords, newerThanDate,
offset, limit + 1, order, true, onlyIds, tag);
for (FeedEntryStatus status : list) {
entries.getEntries().add(
@@ -192,16 +193,20 @@ public class CategoryREST extends AbstractREST {
@Produces(MediaType.APPLICATION_XML)
@SecurityCheck(value = Role.USER, apiKeyAllowed = true)
public Response getCategoryEntriesAsFeed(
@ApiParam(value = "id of the category, 'all' or 'starred'", required = true) @QueryParam("id") String id) {
@ApiParam(value = "id of the category, 'all' or 'starred'", required = true) @QueryParam("id") String id,
@ApiParam(value = "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);
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);
Response response = getCategoryEntries(id, readType, newerThan, offset, limit, order, keywords, onlyIds, excludedSubscriptionIds,
tag);
if (response.getStatus() != Status.OK.getStatusCode()) {
return response;
}

View File

@@ -1,17 +1,23 @@
package com.commafeed.frontend.rest.resources;
import java.util.List;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
import com.commafeed.backend.dao.FeedEntryStatusDAO;
import com.commafeed.backend.dao.FeedEntryTagDAO;
import com.commafeed.backend.dao.FeedSubscriptionDAO;
import com.commafeed.backend.services.ApplicationSettingsService;
import com.commafeed.backend.services.FeedEntryService;
import com.commafeed.backend.services.FeedEntryTagService;
import com.commafeed.frontend.model.request.MarkRequest;
import com.commafeed.frontend.model.request.MultipleMarkRequest;
import com.commafeed.frontend.model.request.StarRequest;
import com.commafeed.frontend.model.request.TagRequest;
import com.google.common.base.Preconditions;
import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiOperation;
@@ -30,6 +36,12 @@ public class EntryREST extends AbstractREST {
@Inject
FeedSubscriptionDAO feedSubscriptionDAO;
@Inject
FeedEntryTagDAO feedEntryTagDAO;
@Inject
FeedEntryTagService feedEntryTagService;
@Inject
ApplicationSettingsService applicationSettingsService;
@@ -71,4 +83,24 @@ public class EntryREST extends AbstractREST {
return Response.ok().build();
}
@Path("/tags")
@GET
@ApiOperation(value = "Get list of tags for the user", notes = "Get list of tags for the user")
public Response getTags() {
List<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.SubscribeRequest;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import com.sun.syndication.feed.opml.Opml;
import com.sun.syndication.feed.synd.SyndEntry;
@@ -167,8 +168,8 @@ public class FeedREST extends AbstractREST {
entries.setErrorCount(subscription.getFeed().getErrorCount());
entries.setFeedLink(subscription.getFeed().getLink());
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(Arrays.asList(subscription), unreadOnly, keywords,
newerThanDate, offset, limit + 1, order, true, onlyIds);
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(getUser(), Arrays.asList(subscription), unreadOnly,
keywords, newerThanDate, offset, limit + 1, order, true, onlyIds, null);
for (FeedEntryStatus status : list) {
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")
@Produces(MediaType.APPLICATION_XML)
@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);
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);
Response response = getFeedEntries(id, readType, newerThan, offset, limit, order, keywords, onlyIds);
if (response.getStatus() != Status.OK.getStatusCode()) {
return response;
}
@@ -262,7 +265,8 @@ public class FeedREST extends AbstractREST {
try {
info = fetchFeedInternal(req.getUrl());
} 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();
}
@@ -271,11 +275,7 @@ public class FeedREST extends AbstractREST {
@GET
@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() {
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(getUser());
for (FeedSubscription sub : subs) {
Feed feed = sub.getFeed();
taskGiver.add(feed, true);
}
feedSubscriptionService.refreshAll(getUser());
return Response.ok().build();
}
@@ -374,8 +374,10 @@ public class FeedREST extends AbstractREST {
try {
url = fetchFeedInternal(url).getUrl();
FeedCategory category = CategoryREST.ALL.equals(req.getCategoryId()) ? null : feedCategoryDAO.findById(Long.valueOf(req
.getCategoryId()));
FeedCategory category = null;
if (req.getCategoryId() != null && !CategoryREST.ALL.equals(req.getCategoryId())) {
category = feedCategoryDAO.findById(Long.valueOf(req.getCategoryId()));
}
FeedInfo info = fetchFeedInternal(url);
feedSubscriptionService.subscribe(getUser(), info.getUrl(), req.getTitle(), category);
} catch (Exception e) {

View File

@@ -79,6 +79,7 @@ public class UserREST extends AbstractREST {
s.setTheme(settings.getTheme());
s.setCustomCss(settings.getCustomCss());
s.setLanguage(settings.getLanguage());
s.setScrollSpeed(settings.getScrollSpeed());
} else {
s.setReadingMode(ReadingMode.unread.name());
s.setReadingOrder(ReadingOrder.desc.name());
@@ -88,6 +89,7 @@ public class UserREST extends AbstractREST {
s.setSocialButtons(true);
s.setScrollMarks(true);
s.setLanguage("en");
s.setScrollSpeed(400);
}
return Response.ok(s).build();
}
@@ -116,6 +118,7 @@ public class UserREST extends AbstractREST {
s.setCustomCss(settings.getCustomCss());
s.setSocialButtons(settings.isSocialButtons());
s.setLanguage(settings.getLanguage());
s.setScrollSpeed(settings.getScrollSpeed());
userSettingsDAO.saveOrUpdate(s);
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"?>
<persistence version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
@@ -9,6 +8,7 @@
<jta-data-source>${jpa.datasource.name}</jta-data-source>
<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
<properties>
<property name="openejb.jpa.auto-scan" value="true" />
<property name="format_sql" value="true" />
<property name="use_sql_comments" value="true" />
@@ -22,26 +22,17 @@
<property name="hibernate.generate_statistics" value="true" />
<property name="hibernate.cache.use_second_level_cache"
value="${jpa.cache}" />
<property name="hibernate.cache.use_second_level_cache" value="${jpa.cache}" />
<property name="hibernate.cache.use_query_cache" value="${jpa.cache}" />
<property name="hibernate.cache.region.factory_class"
value="org.hibernate.cache.infinispan.InfinispanRegionFactory" />
<property name="hibernate.cache.infinispan.statistics"
value="true" />
<property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.infinispan.InfinispanRegionFactory" />
<property name="hibernate.cache.infinispan.statistics" value="true" />
<property name="hibernate.cache.infinispan.entity.eviction.strategy"
value="LRU" />
<property
name="hibernate.cache.infinispan.entity.eviction.wake_up_interval"
value="2000" />
<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" />
<property name="hibernate.cache.infinispan.entity.eviction.strategy" value="LRU" />
<property name="hibernate.cache.infinispan.entity.eviction.wake_up_interval" value="2000" />
<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>
</persistence-unit>

View File

@@ -357,6 +357,7 @@
</changeSet>
<changeSet author="athou" id="status-cleanup">
<validCheckSum>7:cf40ae235c2d4086c5fa6ac64102c6a9</validCheckSum>
<delete tableName="FEEDENTRYSTATUSES">
<where>read_status = false and starred = false</where>
</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.2.xml" />
<include file="changelogs/db.changelog-1.3.xml" />
<include file="changelogs/db.changelog-1.4.xml" />
</databaseChangeLog>

View File

@@ -6,6 +6,7 @@ global.download=تحميل
global.link=رابط
global.bookmark=مرجعية
global.close=أغلق
global.tags=Tags ####### Needs translation
tree.subscribe=اشترك
tree.import=استورد
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=الترتيب حسب التاريخ تصاعدي / ت
toolbar.titles_only=العناوين فقط
toolbar.expanded_view=عرض موسع
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_week=العناصر الأقدم من أسبوع
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.scroll_marks=In expanded view, scrolling through entries mark them as read
settings.appearance=Appearance
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=Theme
settings.submit_your_theme=Submit your theme
settings.custom_css=Custom CSS
@@ -83,7 +87,10 @@ details.queued_for_refresh=Queued for refresh
details.feed_url=Feed URL
details.generate_api_key_first=Generate an API key in your profile first.
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.tag_details=Tag details ####### Needs translation
details.parent_category=Parent category
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.opml_export=OPML export
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.keyboard_shortcuts=Keyboard shortcuts

View File

@@ -6,6 +6,7 @@ global.download = Stáhnout
global.link = Odkaz
global.bookmark = Záložky
global.close = Zavřít
global.tags=Tags ####### Needs translation
tree.subscribe = Nový odběr
tree.import = Importovat
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc = Seřadit podle nejnovějšího/nejstaršího
toolbar.titles_only = Zobrazit jenom titulky
toolbar.expanded_view = Rozšířený náhled
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_week = Položky starší než týden
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.scroll_marks = Skrolování v rozšířeném náhledu označí položky jako přečtené
settings.appearance = Vzhled
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme = Motiv
settings.submit_your_theme = Nahrát vlastní motiv
settings.custom_css = Vlastní motiv (CSS)
@@ -83,7 +87,10 @@ details.queued_for_refresh = Ve frontě na obnovu
details.feed_url = URL RSS zdroje
details.generate_api_key_first = Vygenerujte si API klíč na stránce vašeho profilu.
details.unsubscribe = Odhlásit odběr.
details.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.tag_details=Tag details ####### Needs translation
details.parent_category = Hlavní kategorie
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.opml_export = exportovat do formátu OPML
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.keyboard_shortcuts = Klávesové zkratky

View File

@@ -6,6 +6,7 @@ global.download=Lawrlwytho
global.link=Dolen
global.bookmark=Nod tudalen
global.close=Cau
global.tags=Tags ####### Needs translation
tree.subscribe=Tanysgrifio
tree.import=Mewnforio
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Trefnu yn ôl dyddiad
toolbar.titles_only=Teitlau yn unig
toolbar.expanded_view=Golwg estynedig
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_week=Eitemau hyn nag wythnos
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.scroll_marks=Marcio eitemau fel wedi eu darllen wrth sgrolio drwyddynt yn y golwg estynedig ###### Defnyddio gystrawen debyg i'r ddau uwch.
settings.appearance=Golwg
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=Thema
settings.submit_your_theme=Cyflwyna dy thema
settings.custom_css=CSS wedi'i addasu
@@ -83,7 +87,10 @@ details.queued_for_refresh=Ciwiwyd i'w adnewyddu
details.feed_url=URL Ffrwd
details.generate_api_key_first=Rhaid creu allwedd API yn dy broffil yn gyntaf.
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.tag_details=Tag details ####### Needs translation
details.parent_category=Categori rhiant
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.opml_export=Allforio OPML
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.keyboard_shortcuts=Llwybr byr bysellfwrdd

View File

@@ -6,6 +6,7 @@ global.download=Hent
global.link=Link
global.bookmark=Bogmærke
global.close=Luk
global.tags=Tags ####### Needs translation
tree.subscribe=Abonner
tree.import=Importer
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Sorter efter dato ny/gammel
toolbar.titles_only=Kun titler
toolbar.expanded_view=Udvidet visning
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_week=Artikler ældere end én uge
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.scroll_marks=I udvidet visning, marker artikler som læste når der rulles forbi dem
settings.appearance=Udseende
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=Tema
settings.submit_your_theme=Indsend dit tema
settings.custom_css=Brugerdefineret CSS
@@ -83,7 +87,10 @@ details.queued_for_refresh=I kø til opdatering
details.feed_url=URL for abonnement
details.generate_api_key_first=Generer en API nøgle i din profil først.
details.unsubscribe=Afmeld abonnement
details.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.tag_details=Tag details ####### Needs translation
details.parent_category=Overordnet kategori
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.opml_export=OPML eksport
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.keyboard_shortcuts=Tastaturgenveje

View File

@@ -6,6 +6,7 @@ global.download=Herunterladen
global.link=Link
global.bookmark=Lesezeichen
global.close=Schließen
global.tags=Tags ####### Needs translation
tree.subscribe=Abonnieren
tree.import=Importieren
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Nach Datum sortieren (auf-/absteigend)
toolbar.titles_only=Nur Überschriften
toolbar.expanded_view=Ausgedehnte Ansicht
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_week=Artikel älter als eine Woche
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.scroll_marks=In der ausgedehnten Ansicht werden Artikel beim Scrollen als gelesen markiert
settings.appearance=Aussehen
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=Theme
settings.submit_your_theme=Füg dein Theme hinzu
settings.custom_css=Eigenes CSS
@@ -77,13 +81,16 @@ details.name=Name
details.category=Kategorie
details.position=Position
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.queued_for_refresh=Wartet auf Aktualisierung
details.feed_url=Feed Adresse
details.generate_api_key_first=Generiere zuerst einen API Schlüssel in deinem Profil.
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.tag_details=Tag details ####### Needs translation
details.parent_category=Übergeordnete Kategorie
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.opml_export=OPML exportieren
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.keyboard_shortcuts=Tastatur Kurzbefehle

View File

@@ -6,6 +6,7 @@ global.download=Download
global.link=Link
global.bookmark=Bookmark
global.close=Close
global.tags=Tags
tree.subscribe=Subscribe
tree.import=Import
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Sort by date asc/desc
toolbar.titles_only=Titles only
toolbar.expanded_view=Expanded view
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_week=Items older than a week
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.scroll_marks=In expanded view, scrolling through entries mark them as read
settings.appearance=Appearance
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds)
settings.scroll_speed.help=set to 0 to disable
settings.theme=Theme
settings.submit_your_theme=Submit your theme
settings.custom_css=Custom CSS
@@ -83,7 +87,10 @@ details.queued_for_refresh=Queued for refresh
details.feed_url=Feed URL
details.generate_api_key_first=Generate an API key in your profile first.
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.tag_details=Tag details
details.parent_category=Parent category
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.opml_export=OPML export
profile.delete_account=Delete account
profile.delete_account_confirmation=Delete your account? There's no turning back!
about.rest_api=REST API
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.go_to_all=go to the All 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.bookmark=Marcador
global.close=Close ####### Needs translation
global.tags=Tags ####### Needs translation
tree.subscribe=Subscribir
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.expanded_view=Vista Expandida
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_week=Artículos más de una semana
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.scroll_marks=En vista expandida, el desplazamiento por las entradas las marca como leídas
settings.appearance=Appearance ####### Needs translation
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=Theme ####### Needs translation
settings.submit_your_theme=Submit your theme ####### Needs translation
settings.custom_css=CSS Personalizado
@@ -83,7 +87,10 @@ details.queued_for_refresh=Queued for refresh ####### Needs translation
details.feed_url=URL del Canal
details.generate_api_key_first=Genera una llave API en tu perfil primero.
details.unsubscribe=Terminar subscripción
details.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.tag_details=Tag details ####### Needs translation
details.parent_category=Categoría principal
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.opml_export=Exportación de OPML
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.keyboard_shortcuts=Atajos de teclado

View File

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

View File

@@ -6,6 +6,7 @@ global.download=Lataa
global.link=Linkki
global.bookmark=Kirjanmerkki
global.close=Sulje
global.tags=Tagit
tree.subscribe=Tilaa syöte
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.expanded_view=Laajennettu näkymä
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_week=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.no_unread_items=ei sisällä lukemattomia otsikoita.
view.mark_up_to_here=Merkitse luetuksi tähän asti
view.search_for=searching for: ####### Needs translation
view.no_search_results=No match found for the requested keywords ####### Needs translation
view.search_for=Etsi sanoilla:
view.no_search_results=Ei tuloksia annetuilla hakusanoilla.
feedsearch.hint=Kirjoita syötteen nimi...
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.scroll_marks=Laajennetussa näkymässä otsikoiden selaaminen merkitsee ne luetuiksi
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.submit_your_theme=Lähetä oma teemasi
settings.custom_css=Oma CSS
@@ -77,13 +81,16 @@ details.name=Nimi
details.category=Kansio
details.position=Paikka
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.queued_for_refresh=Jonossa päivitettäväksi
details.feed_url=Syötteen osoite
details.generate_api_key_first=Luo API-avain profiilissasi.
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.tag_details=Tagin tiedot
details.parent_category=Yläkansio
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.opml_export=OPML vienti
profile.delete_account=Poista tunnus
profile.delete_account_confirmation=Haluatko varmasti poistaa tunnuksesi? Tätä ei voi perua!
about.rest_api=REST-API
about.keyboard_shortcuts=Näppäinoikotiet

View File

@@ -6,6 +6,7 @@ global.download=Télécharger
global.link=Lien
global.bookmark=Favoris
global.close=Fermer
global.tags=Tags ####### Needs translation
tree.subscribe=S'abonner
tree.import=Importer
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Trier par date croissante/décroissante
toolbar.titles_only=Titres uniquement
toolbar.expanded_view=Vue étendue
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_week=Articles de plus d'une semaine
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.scroll_marks=En mode de lecture étendu, marquer comme lu les éléments lorsque la fenêtre descend.
settings.appearance=Apparence
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=Thème
settings.submit_your_theme=Soumettez votre thème.
settings.custom_css=CSS personnelle
@@ -83,7 +87,10 @@ details.queued_for_refresh=En file d'attente
details.feed_url=URL du flux
details.generate_api_key_first=Générez une clé API dans votre profil d'abord.
details.unsubscribe=Se désabonner
details.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.tag_details=Tag details ####### Needs translation
details.parent_category=Catégorie parente
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.opml_export=Export du fichier OPML
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.keyboard_shortcuts=Raccourcis clavier

View File

@@ -6,6 +6,7 @@ global.download=Descargar
global.link=Ligazón
global.bookmark=Marcador
global.close=Pechar
global.tags=Tags ####### Needs translation
tree.subscribe=Subscribir
tree.import=Importar
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Ordenar por data asc/desc
toolbar.titles_only=Só títulos
toolbar.expanded_view=Vista expandida
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_week=Artigos de máis de unha semana
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.scroll_marks=En vista expandida, o desplazamento polas entradas márcaas como lidas.
settings.appearance=Aspecto
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=Decorado
settings.submit_your_theme=Envíe o seu decorado
settings.custom_css=CSS Personalizado
@@ -83,7 +87,10 @@ details.queued_for_refresh=En cola para actualizar
details.feed_url=URL da fonte
details.generate_api_key_first=Antes debes xerar unha chave API no teu perfil.
details.unsubscribe=Rematar suscripción
details.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.tag_details=Tag details ####### Needs translation
details.parent_category=Categoría principal
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.opml_export=Exportación de OPML
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.keyboard_shortcuts=Atallos de teclado

View File

@@ -6,6 +6,7 @@ global.download=جیرأکش
global.link=خال
global.bookmark=بوکمارک
global.close=دَوَستن
global.tags=Tags ####### Needs translation
tree.subscribe=مشترک ببید
tree.import=درینأدأن
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=تاریخˇ سر دچئن
toolbar.titles_only=خالی تیتران
toolbar.expanded_view=واشاده نما
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_week=یک هفته پیشترˇ مطالب
toolbar.mark_all_older_two_weeks=چن هفته پیشترˇ مطالب
@@ -66,6 +68,8 @@ settings.general.show_unread=تنها خوراک‌ها و دسته‌های ر
settings.general.social_buttons=نشان‌دادن دکمه‌های اشتراک‌گذاری در شبکه‌های اجتماعی
settings.general.scroll_marks=در نمای گسترش‌یافته، لغزیدن بر روی مطالب به‌عنوان نشانه‌گذاری به‌عنوان خوانده‌شده در نظر گرفته‌شوند.
settings.appearance=ظاهر
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=پوسته
settings.submit_your_theme=شیمه پوستهٰ اوسه کونید
settings.custom_css=سی‌اس‌اس شخصی‌سازی‌شده
@@ -83,7 +87,10 @@ details.queued_for_refresh=منتظر برای بروزرسانی
details.feed_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=Tag details ####### Needs translation
details.parent_category=پئرˇ جرگه
profile.user_name=کاربری نام
@@ -98,6 +105,7 @@ 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=وئر زئنˇ کلیدان

View File

@@ -1,149 +1,157 @@
global.save=Mentés
global.cancel=Mégsem
global.delete=Törlés
global.required=Szükséges
global.download=Letöltés
global.link=Link
global.bookmark=Könyvjelző
global.close=Bezár
tree.subscribe=Feliratkozás
tree.import=Importálás
tree.new_category=Új kategória
tree.all=Összes
tree.starred=Csillagozott
subscribe.feed_url=Hírcsatorna URL
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_download=Alternatívaként, feltöltheti a subscriptions.xml fájlt.
import.google_download_link=Letöltheti innen.
import.xml_file=OPML Fájl
new_category.name=Név
new_category.parent=Szülő
toolbar.unread=Olvasatlan
toolbar.all=Összes
toolbar.previous_entry=Előző elem
toolbar.next_entry=Következő elem
toolbar.refresh=Frissítés
toolbar.refresh_all=Force refresh all my feeds ####### Needs translation
toolbar.sort_by_asc_desc=Rendezés időrend szerint
toolbar.titles_only=Csak cím
toolbar.expanded_view=Részletes nézet
toolbar.mark_all_as_read=Az összes megjelölése olvasottként
toolbar.mark_all_older_day=Régebbiek, mint egy nap
toolbar.mark_all_older_week=Régebbiek, mint egy hét
toolbar.mark_all_older_two_weeks=Régebbiek, mint két hét
toolbar.settings=Beállítások
toolbar.profile=Profil
toolbar.admin=Admin
toolbar.about=Névjegy
toolbar.logout=Kilépés
toolbar.donate=Anyagi támogatás
view.entry_source=from ####### Needs translation
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.keep_unread=Megtartása olvasatlanként
view.no_unread_items=nincsen olvasatlan eleme.
view.mark_up_to_here=Mark as read up to here ####### Needs translation
view.search_for=searching for: ####### Needs translation
view.no_search_results=No match found for the requested keywords ####### Needs translation
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.
feedsearch.result_prefix=Az ön feliratkozásai:
settings.general=Általános
settings.general.language=Nyelv
settings.general.language.contribute=Segítsen a fordításban
settings.general.show_unread=Mutassa azokat a hírcsatornákat és kategóriákat amelyekben nincsen olvasatlan bejegyzés
settings.general.social_buttons=Mutassa a közösségi oldalak megosztás gombjait
settings.general.scroll_marks=Kiterjesztett nézetben, görgetéssel olvasottként jelöli meg a bejegyzést
settings.appearance=Megjelenés
settings.theme=Téma
settings.submit_your_theme=Küldje el a témáját
settings.custom_css=Saját CSS
details.feed_details=Hírcsatorna részletei
details.url=URL
details.website=Website ####### Needs translation
details.name=Név
details.category=Kategória
details.position=Position ####### Needs translation
details.last_refresh=Utolsó frissítés
details.message=Last refresh message ####### Needs translation
details.next_refresh=Következő frissítés
details.queued_for_refresh=Frissítésre vár
details.feed_url=Hírcsatorna URL
details.generate_api_key_first=A profiljában először egy API kulcsot kell generálnia.
details.unsubscribe=Leiratkozás
details.category_details=Kategória részletei
details.parent_category=Szülő kategória
profile.user_name=Felhasználói név
profile.email=E-mail
profile.change_password=Jelszó megváltoztatás
profile.confirm_password=Jelszó megerősítése
profile.minimum_6_chars=Legalább 8 karakter
profile.passwords_do_not_match=A jelszavak nem egyeznek
profile.api_key=API kulcs
profile.api_key_not_generated=Még nincsen generálva
profile.generate_new_api_key=Új API kulcs generálása
profile.generate_new_api_key_info=A jelszó megváltoztatása új API kulcsot generál
profile.opml_export=OPML exportálása
profile.delete_account=Fiók törlése
about.rest_api=REST API
about.keyboard_shortcuts=Gyorsbillentyűk
about.version=CommaFeed version ####### Needs translation
about.line1_prefix=A CommaFeed egy nyílt forrású projekt. A forrás megtalálható a
about.line1_suffix=oldalán.
about.line2_prefix=Ha hibába ütközik, kérjük jelentse azt a
about.line2_suffix=projekt oldalán.
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.goodies=Hasznos dolgok
about.goodies.android_app=Android app ####### Needs translation
about.goodies.subscribe_url=Feliratkozás az URL-re
about.goodies.chrome_extension=Chrome bővítmény
about.goodies.firefox_extension=Firefox kiterjesztés
about.goodies.opera_extension=Opera kiterjesztés
about.goodies.subscribe_bookmarklet=Feliratkozás bookmarklet hozzáadása (klikkeléssel)
about.goodies.subscribe_bookmarklet_asc=Oldest first ####### Needs translation
about.goodies.subscribe_bookmarklet_desc=Newest first ####### Needs translation
about.goodies.next_unread_bookmarklet=Következő olvasatlan elem bookmarklet (húzza fel a könyvjelzősávba)
about.translation=Fordítás
about.translation.message=Segítségét kérjük a CommaFeed fordításához.
about.translation.link=Nézze meg, hogyan tud segíteni ebben.
about.announcements=Bejelentések
about.rest_api.line1=A CommaFeed a JAX-RS-re és az AngularJS-re épül. Ezért a RESTA API elérhető.
about.rest_api.link_to_documentation=Link a dokumentációhoz.
about.shortcuts.mouse_middleclick=középső egérgomb
about.shortcuts.open_next_entry=következő hír megnyitása
about.shortcuts.open_previous_entry=előző hír megnyitása
about.shortcuts.spacebar=space/shift+space ####### Needs translation
about.shortcuts.move_page_down_up=moves the page down/up ####### Needs translation
about.shortcuts.focus_next_entry=megnyitás nélkül fókuszál a övetkező elemre
about.shortcuts.focus_previous_entry=megnyitás nélkül fókuszál az előző elemre
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.open_close_current_entry=a jelenlegi elem megnyitása/bezárása
about.shortcuts.open_current_entry_in_new_window=a jelenlegi elem megnyitása új ablakban
about.shortcuts.open_current_entry_in_new_window_background=a jelenlegi elem megnyitása a háttérben, új ablakban
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=toggle full screen mode ####### Needs translation
about.shortcuts.font_size=increase/decrease font size of the current entry ####### Needs translation
about.shortcuts.go_to_all=go to the All view ####### Needs translation
about.shortcuts.go_to_starred=go to the Starred view ####### Needs translation
about.shortcuts.feed_search=név szerinti keresés a hírcsatornák között
global.save=Mentés
global.cancel=Mégsem
global.delete=Törlés
global.required=Szükséges
global.download=Letöltés
global.link=Link
global.bookmark=Könyvjelző
global.close=Bezár
global.tags=Címkék
tree.subscribe=Feliratkozás
tree.import=Importálás
tree.new_category=Új kategória
tree.all=Összes
tree.starred=Csillagozott
subscribe.feed_url=Hírcsatorna URL
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_download=Alternatívaként, feltöltheti a subscriptions.xml fájlt.
import.google_download_link=Letöltheti innen.
import.xml_file=OPML Fájl
new_category.name=Név
new_category.parent=Szülő
toolbar.unread=Olvasatlan
toolbar.all=Összes
toolbar.previous_entry=Előző elem
toolbar.next_entry=Következő elem
toolbar.refresh=Frissítés
toolbar.refresh_all=Force refresh all my feeds ####### Needs translation
toolbar.sort_by_asc_desc=Rendezés időrend szerint
toolbar.titles_only=Csak cím
toolbar.expanded_view=Részletes nézet
toolbar.mark_all_as_read=Az összes megjelölése olvasottként
toolbar.mark_all_older_12_hours=Régebbiek 12 óránál
toolbar.mark_all_older_day=Régebbiek, mint egy nap
toolbar.mark_all_older_week=Régebbiek, mint egy hét
toolbar.mark_all_older_two_weeks=Régebbiek, mint két hét
toolbar.settings=Beállítások
toolbar.profile=Profil
toolbar.admin=Admin
toolbar.about=Névjegy
toolbar.logout=Kilépés
toolbar.donate=Anyagi támogatás
view.entry_source=forrás
view.entry_author=szerző
view.error_while_loading_feed=Hiba történt ennek a hírcsatornának a betöltésekor
view.keep_unread=Megtartása olvasatlanként
view.no_unread_items=nincsen olvasatlan eleme.
view.mark_up_to_here=Megjelölés olvasottnak eddig
view.search_for=keresés erre:
view.no_search_results=Nem található semmi erre a keresett szóra
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.
feedsearch.result_prefix=Az ön feliratkozásai:
settings.general=Általános
settings.general.language=Nyelv
settings.general.language.contribute=Segítsen a fordításban
settings.general.show_unread=Mutassa azokat a hírcsatornákat és kategóriákat amelyekben nincsen olvasatlan bejegyzés
settings.general.social_buttons=Mutassa a közösségi oldalak megosztás gombjait
settings.general.scroll_marks=Kiterjesztett nézetben, görgetéssel olvasottként jelöli meg a bejegyzést
settings.appearance=Megjelenés
settings.scroll_speed=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
settings.theme=Téma
settings.submit_your_theme=Küldje el a témáját
settings.custom_css=Saját CSS
details.feed_details=Hírcsatorna részletei
details.url=URL
details.website=Weboldal
details.name=Név
details.category=Kategória
details.position=Pozició
details.last_refresh=Utolsó frissítés
details.message=Utolsó frissítési üzenet
details.next_refresh=Következő frissítés
details.queued_for_refresh=Frissítésre vár
details.feed_url=Hírcsatorna URL
details.generate_api_key_first=A profiljában először egy API kulcsot kell generálnia.
details.unsubscribe=Leiratkozás
details.unsubscribe_confirmation=Biztos, hogy le akar iratkozni errről a csatornáról?
details.delete_category_confirmation=Biztos, hog törölni akarja ezt a kategóriát?
details.category_details=Kategória részletei
details.tag_details=Címke részletei
details.parent_category=Szülő kategória
profile.user_name=Felhasználói név
profile.email=E-mail
profile.change_password=Jelszó megváltoztatás
profile.confirm_password=Jelszó megerősítése
profile.minimum_6_chars=Legalább 8 karakter
profile.passwords_do_not_match=A jelszavak nem egyeznek
profile.api_key=API kulcs
profile.api_key_not_generated=Még nincsen generálva
profile.generate_new_api_key=Új API kulcs generálása
profile.generate_new_api_key_info=A jelszó megváltoztatása új API kulcsot generál
profile.opml_export=OPML exportálása
profile.delete_account=Fiók törlése
profile.delete_account_confirmation=Törli a fiókját? Innen már nincs visszatérés!
about.rest_api=REST API
about.keyboard_shortcuts=Gyorsbillentyűk
about.version=CommaFeed verzió
about.line1_prefix=A CommaFeed egy nyílt forrású projekt. A forrás megtalálható a
about.line1_suffix=oldalán.
about.line2_prefix=Ha hibába ütközik, kérjük jelentse azt a
about.line2_suffix=projekt oldalán.
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.goodies=Hasznos dolgok
about.goodies.android_app=Android alkalmazás
about.goodies.subscribe_url=Feliratkozás az URL-re
about.goodies.chrome_extension=Chrome bővítmény
about.goodies.firefox_extension=Firefox kiterjesztés
about.goodies.opera_extension=Opera kiterjesztés
about.goodies.subscribe_bookmarklet=Feliratkozás bookmarklet hozzáadása (klikkeléssel)
about.goodies.subscribe_bookmarklet_asc=Régebbiek először
about.goodies.subscribe_bookmarklet_desc=Újak először
about.goodies.next_unread_bookmarklet=Következő olvasatlan elem bookmarklet (húzza fel a könyvjelzősávba)
about.translation=Fordítás
about.translation.message=Segítségét kérjük a CommaFeed fordításához.
about.translation.link=Nézze meg, hogyan tud segíteni ebben.
about.announcements=Bejelentések
about.rest_api.line1=A CommaFeed a JAX-RS-re és az AngularJS-re épül. Ezért a RESTA API elérhető.
about.rest_api.link_to_documentation=Link a dokumentációhoz.
about.shortcuts.mouse_middleclick=középső egérgomb
about.shortcuts.open_next_entry=következő hír megnyitása
about.shortcuts.open_previous_entry=előző hír megnyitása
about.shortcuts.spacebar=szóköz/shift+szóköz
about.shortcuts.move_page_down_up=fel/le lépkedhet az oldalon
about.shortcuts.focus_next_entry=megnyitás nélkül fókuszál a övetkező elemre
about.shortcuts.focus_previous_entry=megnyitás nélkül fókuszál az előző elemre
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.open_close_current_entry=a jelenlegi elem megnyitása/bezárása
about.shortcuts.open_current_entry_in_new_window=a jelenlegi elem megnyitása új ablakban
about.shortcuts.open_current_entry_in_new_window_background=a jelenlegi elem megnyitása a háttérben, új ablakban
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.bookmark=Segnalibro
global.close=Chiudi
global.tags=Tags ####### Needs translation
tree.subscribe=Iscriviti
tree.import=Importa
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Sort by date asc/desc
toolbar.titles_only=Solo titoli
toolbar.expanded_view=Espandi
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_week=Elementi più vecchi di una settimana
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.scroll_marks=Marca come letto quando scorri
settings.appearance=Appearance ####### Needs translation
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=Tema
settings.submit_your_theme=Sottoponi il tuo tema
settings.custom_css=Css modificato
@@ -83,7 +87,10 @@ details.queued_for_refresh=In attesa per l'aggiornamento
details.feed_url=Feed URL
details.generate_api_key_first=Generate an API key in your profile first.
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.tag_details=Tag details ####### Needs translation
details.parent_category=Parent category
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.opml_export=Esporta OPML
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.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.bookmark=Bookmark ####### Needs translation
global.close=Close ####### Needs translation
global.tags=Tags ####### Needs translation
tree.subscribe=구독
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.expanded_view=Expanded view ####### Needs translation
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_week=Items older than a week ####### 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.scroll_marks=Expanded View에서 스크롤하면 항목들을 읽음으로 저장하기
settings.appearance=Appearance ####### Needs translation
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=Theme ####### Needs translation
settings.submit_your_theme=Submit your theme ####### Needs translation
settings.custom_css=커스톰 CSS
@@ -83,7 +87,10 @@ details.queued_for_refresh=Queued for refresh ####### Needs translation
details.feed_url=피드 유알엘
details.generate_api_key_first=당신의 프로필을 위해 API Key를 먼저 생성하세요.
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=Tag details ####### Needs translation
details.parent_category=부모 카테고리
profile.user_name=사용자 이름
@@ -98,6 +105,7 @@ profile.generate_new_api_key=API Key 생성하기
profile.generate_new_api_key_info=비밀번호를 변경하면 새로운 API Key가 생성됩니다.
profile.opml_export=OPML export ####### Needs translation
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=단축기

View File

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

View File

@@ -6,6 +6,7 @@ global.download=Muat turun
global.link=Pautan
global.bookmark=Bookmark
global.close=Tutup
global.tags=Tags ####### Needs translation
tree.subscribe=Langgan
tree.import=Import
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Aturkan mengikut tarikh (baru/lama)
toolbar.titles_only=Tajuk sahaja
toolbar.expanded_view=Wide view
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_week=Lebih lama daripada seminggu
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.scroll_marks=Dalam wide view, tanda item dibaca ketika scrolling
settings.appearance=Rupa
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=Tema
settings.submit_your_theme=Muat naik tema anda
settings.custom_css=Custom CSS
@@ -83,7 +87,10 @@ details.queued_for_refresh=Diaturkan untuk refresh
details.feed_url=URL Feed
details.generate_api_key_first=Janakan API key dalam profil anda dahulu.
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.tag_details=Tag details ####### Needs translation
details.parent_category=Kategori induk
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.opml_export=Export OPML
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.keyboard_shortcuts=Pintasan papan kekunci

View File

@@ -6,6 +6,7 @@ global.download=Last ned
global.link=Lenke
global.bookmark=Bokmerke
global.close=Lukk
global.tags=Tags ####### Needs translation
tree.subscribe=Abonner
tree.import=Importer
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Sorter etter dato ny/gammel
toolbar.titles_only=Kun titler
toolbar.expanded_view=Utvidet visning
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_week=Artikler eldre enn én uke
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.scroll_marks=I utvidet visning, merk artikler som leste når du blar deg forbi dem.
settings.appearance=Utseende
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=Drakt
settings.submit_your_theme=Legg til egen drakt
settings.custom_css=Egendefinert CSS
@@ -83,7 +87,10 @@ details.queued_for_refresh=I kø for oppdatering
details.feed_url=URL for abonnement
details.generate_api_key_first=Generer en API-nøkkel under profilinnstillinger først.
details.unsubscribe=Avslutt abonnement
details.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.tag_details=Tag details ####### Needs translation
details.parent_category=Overordnet kategori
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.opml_export=OPML-eksport
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.keyboard_shortcuts=Hurtigtaster

View File

@@ -6,6 +6,7 @@ global.download=Download
global.link=Link
global.bookmark=Bookmark
global.close=Sluiten
global.tags=Tags ####### Needs translation
tree.subscribe=Abonneer
tree.import=Importeer
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Sorteer op datum opl/afl
toolbar.titles_only=Alleen titels
toolbar.expanded_view=Uitgebreide weergave
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_week=Artikelen ouder dan een week
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.scroll_marks=Markeer artikelen als gelezen, wanneer je er doorheen scrollt
settings.appearance=Uiterlijk
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=Thema
settings.submit_your_theme=Stuur thema in
settings.custom_css=Custom CSS
@@ -83,7 +87,10 @@ details.queued_for_refresh=In wachtrij voor vernieuwing
details.feed_url=Feed URL
details.generate_api_key_first=Genereer eerst een API sleutel in je profiel.
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.tag_details=Tag details ####### Needs translation
details.parent_category=Bovenliggende categorie
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.opml_export=OPML export
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.keyboard_shortcuts=Sneltoetsen

View File

@@ -6,6 +6,7 @@ global.download=Last ned
global.link=Lenkje
global.bookmark=Bokmerke
global.close=Lukk
global.tags=Tags ####### Needs translation
tree.subscribe=Abonner
tree.import=Importer
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Sorter etter dato ny/gamal
toolbar.titles_only=Berre titlar
toolbar.expanded_view=Utvida visning
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_week=Artiklar eldre enn éi veke
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.scroll_marks=I utvida visning, merk artiklar som lesne når du blar deg forbi dei.
settings.appearance=Utsjånad
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=Drakt
settings.submit_your_theme=Legg til eiga drakt
settings.custom_css=Skreddarsydd CSS
@@ -83,7 +87,10 @@ details.queued_for_refresh=I kø for oppdatering
details.feed_url=URL for abonnement
details.generate_api_key_first=Generer ein API-nykel under profilinnstillingar fyrst.
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.tag_details=Tag details ####### Needs translation
details.parent_category=Overordna kategori
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.opml_export=OPML-eksport
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.keyboard_shortcuts=Hurtigtastar

View File

@@ -6,6 +6,7 @@ global.download=Pobierz
global.link=Odnośnik
global.bookmark=Zakładka
global.close=Zamknij
global.tags=Tags ####### Needs translation
tree.subscribe=Subskrybuj
tree.import=Importuj
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Sortuj od najnowszego/najstarszego
toolbar.titles_only=Widok listy
toolbar.expanded_view=Widok rozwinięty
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_week=Elementy starsze niż tydzień
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.scroll_marks=W widoku rozwiniętym przewijanie oznacza elementy jako przeczytane
settings.appearance=Wygląd
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=Motyw
settings.submit_your_theme=Wyślij swój motyw
settings.custom_css=Własny styl CSS
@@ -83,7 +87,10 @@ details.queued_for_refresh=W kolejce do odświeżenia
details.feed_url=URL kanału
details.generate_api_key_first=Najpierw wygeneruj klucz API w swoim profilu.
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.tag_details=Tag details ####### Needs translation
details.parent_category=Kategoria nadrzędna
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.opml_export=Eksportuj do pliku OPML
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.keyboard_shortcuts=Skróty klawiszowe

View File

@@ -6,6 +6,7 @@ global.download=Download
global.link=Link
global.bookmark=Favorito
global.close=Fechar
global.tags=Tags ####### Needs translation
tree.subscribe=Inscrever-se
tree.import=Importar
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Ordenar por data cresc/decres
toolbar.titles_only=Somente títulos
toolbar.expanded_view=Modo Expandido
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_week=Itens mais antigos que uma semana
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.scroll_marks=No modo expandido, percorrer os itens marca-os como lidos
settings.appearance=Aparência
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=Tema
settings.submit_your_theme=Envie seu tema
settings.custom_css=CSS personalizado
@@ -83,7 +87,10 @@ details.queued_for_refresh=Na fila para atualizar
details.feed_url=URL do feed
details.generate_api_key_first=Gerar uma chave de API em seu perfil primeiro.
details.unsubscribe=Cancelar inscrição
details.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.tag_details=Tag details ####### Needs translation
details.parent_category=Categoria pai
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.opml_export=Exportar OPML
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.keyboard_shortcuts=Atalhos de teclado

View File

@@ -6,6 +6,7 @@ global.download=Скачать
global.link=Ссылка
global.bookmark=Закладка
global.close=Закрыть
global.tags=Теги
tree.subscribe=Подписаться
tree.import=Импорт
@@ -36,6 +37,7 @@ 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=Записи старше недели
toolbar.mark_all_older_two_weeks=Записи старше двух недель
@@ -52,8 +54,8 @@ view.error_while_loading_feed=Не удалось загрузить ленту
view.keep_unread=Оставить непрочитанным
view.no_unread_items=нет непрочитанных записей.
view.mark_up_to_here=Отметить прочитанным до сюда
view.search_for=searching for: ####### Needs translation
view.no_search_results=No match found for the requested keywords ####### Needs translation
view.search_for=искать:
view.no_search_results=По данному запросу ничего не найдено.
feedsearch.hint=Введите подписку...
feedsearch.help=Используйте клавишу ввода для выбора и стрелки для перемещения.
@@ -66,6 +68,8 @@ 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
@@ -77,13 +81,16 @@ details.name=Название
details.category=Категория
details.position=Позиция
details.last_refresh=Последнее обновление
details.message=Last refresh message ####### Needs translation
details.message=Сообщение последнего обновления
details.next_refresh=Следующее обновление
details.queued_for_refresh=В очереди на обновление
details.feed_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=Подтвердить удаление этой категории?
details.category_details=Информация о категории
details.tag_details=Детали тега
details.parent_category=Родительская категория
profile.user_name=Имя пользователя
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Сгенерировать новый API-ключ
profile.generate_new_api_key_info=После изменения пароля, API-ключ изменится
profile.opml_export=Экспорт OPML
profile.delete_account=Удалить аккаунт
profile.delete_account_confirmation=Удалить ваш аккаунт? Назад пути не будет!
about.rest_api=REST API
about.keyboard_shortcuts=Горячие клавиши

View File

@@ -6,6 +6,7 @@ global.download=Stiahnuť
global.link=Link
global.bookmark=Záložky
global.close=Zavrieť
global.tags=Tagy
tree.subscribe=Odoberať
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.expanded_view=Rozšírený náhľad
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_week=Položky staršie ako týždeň
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.scroll_marks=Scrollovanie v rozšírenom náhľade označí položky ako prečítané
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.submit_your_theme=Nahrať vlastný motív vzhľadu
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.generate_api_key_first=Vygenerujte si API kľúč vo vašom profile.
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.tag_details=Detaily tagu
details.parent_category=Hlavná kategória
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.opml_export=exportovať do formátu OPML
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.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_starred=zobraziť obľúbené položiek
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.bookmark=Bokmärk
global.close=Stäng
global.tags=Taggar
tree.subscribe=Prenumerera
tree.import=Importera
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Sortera efter datum stigande/fallande
toolbar.titles_only=Endast titlar
toolbar.expanded_view=Expanderad vy
toolbar.mark_all_as_read=Markera alla som lästa
toolbar.mark_all_older_12_hours=Poster äldre än 12 timmar
toolbar.mark_all_older_day=Poster äldre än en dag
toolbar.mark_all_older_week=Poster äldre än en vecka
toolbar.mark_all_older_two_weeks=Poster äldre än två veckor
@@ -66,6 +68,8 @@ settings.general.show_unread=Visa prenumerationer och kategorier utan olästa po
settings.general.social_buttons=Visa delningsknappar
settings.general.scroll_marks=I expanderad vy, markera poster som lästa genom att scrolla förbi dem
settings.appearance=Utseende
settings.scroll_speed=Scrollhastighet under navigation mellan poster (i millisekunder)
settings.scroll_speed.help=ställ på 0 för att avaktivera
settings.theme=Tema
settings.submit_your_theme=Skicka in ditt tema
settings.custom_css=Anpassad CSS
@@ -83,7 +87,10 @@ details.queued_for_refresh=I kö för uppdatering
details.feed_url=Prenumerationens URL
details.generate_api_key_first=Skapa en API-nyckel på din profil först.
details.unsubscribe=Avprenumerera
details.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.tag_details=Taggdetaljer
details.parent_category=Överordnad kategori
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.opml_export=OPML-export
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.keyboard_shortcuts=Tangentbordsgenvägar

View File

@@ -6,6 +6,7 @@ global.download=İndir
global.link=Bağlantı
global.bookmark=Yer imi
global.close=Kapat
global.tags=Tags ####### Needs translation
tree.subscribe=Abone ol
tree.import=İçe aktar
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Tarihe göre sırala artan/azalan
toolbar.titles_only=Sadece başlıklar
toolbar.expanded_view=Genişletilmiş görünüm
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_week=Bir 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.scroll_marks=Genişletilmiş görünümde götüntülenen iletileri okunmuş işaretle
settings.appearance=Görünüm
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=Tema
settings.submit_your_theme=Tema gönder
settings.custom_css=Kişiselleştirilmiş CSS
@@ -83,7 +87,10 @@ details.queued_for_refresh=Yenilenmek üzere kuyrukta
details.feed_url=Yayın URL'si
details.generate_api_key_first=Öncelikle profilinizden bir API anahtarı oluşturun.
details.unsubscribe=Aboneliği iptal et
details.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.tag_details=Tag details ####### Needs translation
details.parent_category=Üst kategori
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.opml_export=OPML dışa aktar
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.keyboard_shortcuts=Klavye kısayolları

View File

@@ -6,6 +6,7 @@ global.download=下载
global.link=链接
global.bookmark=书签
global.close=关闭
global.tags=Tags ####### Needs translation
tree.subscribe=订阅
tree.import=导入
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=按日期升序/降序排序
toolbar.titles_only=仅显示标题
toolbar.expanded_view=显示内容
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_week=早于一周的条目
toolbar.mark_all_older_two_weeks=早于两周的条目
@@ -66,6 +68,8 @@ settings.general.show_unread=显示未读的订阅和目录条目
settings.general.social_buttons=显示分享按钮
settings.general.scroll_marks=在扩展视图中,可滚动条目将其标记为已读
settings.appearance=外观
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=主题
settings.submit_your_theme=提交你的主题
settings.custom_css=自定义 CSS 样式
@@ -83,7 +87,10 @@ details.queued_for_refresh=放入等待刷新的队列
details.feed_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=Tag details ####### Needs translation
details.parent_category=上一层目录
profile.user_name=用户名
@@ -98,6 +105,7 @@ 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=重置 API
about.keyboard_shortcuts=快捷键

View File

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

View File

@@ -7,6 +7,7 @@
UserName = sa
Password =
MaxActive = 50
DataSourceCreator bonecp
</Resource>
<!--
@@ -16,6 +17,7 @@
UserName cf
Password cf
MaxActive 50
DataSourceCreator bonecp
</Resource>
-->
@@ -26,6 +28,7 @@
UserName cf
Password cf
MaxActive 50
DataSourceCreator bonecp
</Resource>
-->
@@ -36,6 +39,7 @@
UserName cf
Password cf
MaxActive 50
DataSourceCreator bonecp
</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">
<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/jqueryui/*.js</js>
<js minimize="false">/vendor/jquery-mousewheel/*.js</js>
<js minimize="false">/vendor/bootstrap/*.js</js>
<js minimize="false">/vendor/angularjs/*.js</js>
<js minimize="false">/vendor/angularui/*.js</js>
<js minimize="false">/vendor/angularui-bootstrap/*.js</js>
<js minimize="false">/vendor/angularui-state/*.js</js>
<js minimize="false">/vendor/ui-utils/*.js</js>
<js minimize="false">/vendor/ui-select2/*.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/nggrid/*.js</js>
<js minimize="false">/vendor/nginfinitescroll/*.js</js>
<js minimize="false">/vendor/spinjs/*.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/angularui/*.css</css>
<css minimize="false">/vendor/fontawesome/css/*.css</css>
<css minimize="false">/vendor/zocial/*.css</css>
<css minimize="false">/vendor/readabilicons/css/*.css</css>
<css minimize="false">/vendor/nggrid/*.css</css>
</group>

View File

@@ -1,86 +1,91 @@
<!DOCTYPE html>
<html>
<head>
<title>Swagger UI</title>
<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/screen.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.slideto.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/handlebars-1.0.rc.1.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/swagger.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>
<title>Swagger UI</title>
<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/screen.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.slideto.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/handlebars-1.0.rc.1.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/swagger.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 type="text/javascript">
$(function () {
<script type="text/javascript">
$(function() {
var url = window.location.href;
if (url.indexOf('#')> -1) {
if (url.indexOf('#') > -1) {
url = url.substring(0, url.lastIndexOf('#'));
}
url = url.substring(0, url.length - 1);
url = url.substring(0, url.lastIndexOf('/'));
var link = $('#title-base-url-link')
var link = $('#title-base-url-link');
link.attr('href', url + '/rest');
link.html(link.attr('href'));
url = url + '/api/api-docs/resources';
window.swaggerUi = new SwaggerUi({
discoveryUrl:url,
apiKey:"",
dom_id:"swagger-ui-container",
supportHeaderParams: false,
supportedSubmitMethods: ['get', 'post', 'put', 'delete'],
onComplete: function(swaggerApi, swaggerUi){
if(console) {
console.log("Loaded SwaggerUI")
console.log(swaggerApi);
console.log(swaggerUi);
}
$('pre code').each(function(i, e) {hljs.highlightBlock(e)});
},
onFailure: function(data) {
if(console) {
console.log("Unable to Load SwaggerUI");
console.log(data);
}
},
docExpansion: "none"
});
window.swaggerUi = new SwaggerUi({
discoveryUrl : url,
apiKey : "",
dom_id : "swagger-ui-container",
supportHeaderParams : false,
supportedSubmitMethods : ['get', 'post', 'put', 'delete'],
onComplete : function(swaggerApi, swaggerUi) {
if (console) {
console.log("Loaded SwaggerUI");
console.log(swaggerApi);
console.log(swaggerUi);
}
$('pre code').each(function(i, e) {
hljs.highlightBlock(e);
});
},
onFailure : function(data) {
if (console) {
console.log("Unable to Load SwaggerUI");
console.log(data);
}
},
docExpansion : "none"
});
window.swaggerUi.load();
});
</script>
window.swaggerUi.load();
});
</script>
</head>
<body>
<div id='header'>
<div class="swagger-ui-wrap">
<a id="logo" href="../" style="padding-left: 0px">CommaFeed API</a>
<div id='header'>
<div class="swagger-ui-wrap">
<a id="logo" href="../" style="padding-left: 0px">CommaFeed API</a>
<form id='api_selector'>
<div class='input'><input placeholder="http://example.com/api" id="input_baseUrl" name="baseUrl"
type="text"/></div>
<div class='input'><a id="explore" href="#">Explore</a></div>
</form>
</div>
</div>
<form id='api_selector'>
<div class='input'>
<input placeholder="http://example.com/api" id="input_baseUrl" name="baseUrl" type="text" />
</div>
<div class='input'>
<a id="explore" href="#">Explore</a>
</div>
</form>
</div>
</div>
<div id="message-bar" class="swagger-ui-wrap">
&nbsp;
</div>
<div id="message-bar" class="swagger-ui-wrap">&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">The base URL of the API is <a id="title-base-url-link"></a>.</h3>
<div id="swagger-ui-container" class="swagger-ui-wrap">
</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">
The base URL of the API is
<a id="title-base-url-link"></a>
.
</h3>
<div id="swagger-ui-container" class="swagger-ui-wrap"></div>
</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',
function($scope, FeedService, CategoryService, MobileService) {
module.controller('SubscribeCtrl', ['$scope', '$location', 'FeedService', 'CategoryService', 'MobileService',
function($scope, $location, FeedService, CategoryService, MobileService) {
$scope.opts = {
backdropFade : true,
dialogFade : true
$scope.sub = {
categoryId : 'all'
};
$scope.isOpen = false;
$scope.isOpenImport = false;
$scope.sub = {};
$scope.CategoryService = CategoryService;
$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'
$scope.state = 'ok';
$scope.urlChanged = function() {
@@ -88,59 +68,97 @@ module.controller('SubscribeCtrl', ['$scope', 'FeedService', 'CategoryService',
}
FeedService.subscribe($scope.sub, function() {
CategoryService.init();
$scope.close();
$location.path('/');
}, function(data) {
$scope.state = 'failed';
$scope.sub.title = 'ERROR: ' + data.data;
});
};
$scope.openImport = function() {
$scope.isOpenImport = true;
$scope.back = function() {
$location.path('/');
};
}]);
$scope.closeImport = function() {
$scope.isOpenImport = false;
};
module.controller('NewCategoryCtrl', ['$scope', '$location', 'FeedService', 'CategoryService', 'MobileService',
function($scope, $location, FeedService, CategoryService, MobileService) {
$scope.cat = {};
$scope.CategoryService = CategoryService;
$scope.MobileService = MobileService;
$scope.openCategory = function() {
$scope.isOpenCategory = true;
$scope.cat = {
parentId : 'all'
};
};
$scope.closeCategory = function() {
$scope.isOpenCategory = false;
$scope.cat = {
parentId : 'all'
};
$scope.saveCategory = function() {
CategoryService.add($scope.cat, function() {
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',
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.selectedId = $stateParams._id;
$scope.EntryService = EntryService;
$scope.MobileService = MobileService;
$scope.starred = {
id : 'starred',
name : 'Starred'
};
$scope.tags = [];
$scope.$watch('EntryService.tags', function(newValue, oldValue) {
if (newValue) {
$scope.tags = [];
_.each(newValue, function(e) {
$scope.tags.push({
id : e,
name : e,
isTag : true
});
});
}
}, true);
$scope.$on('$stateChangeSuccess', function() {
$scope.selectedType = $stateParams._type;
$scope.selectedId = $stateParams._id;
});
$scope.resizeCallback = function(event, ui) {
$('.main-content').css('margin-left', $(ui.element).outerWidth(true) + 'px');
};
$timeout(function refreshTree() {
AnalyticsService.track();
CategoryService.refresh(function() {
@@ -201,7 +219,7 @@ module.controller('CategoryTreeCtrl', ['$scope', '$timeout', '$stateParams', '$w
var getCurrentIndex = function(id, type, flat) {
var index = -1;
for ( var i = 0; i < flat.length; i++) {
for (var i = 0; i < flat.length; i++) {
var node = flat[i];
if (node[0] == id && node[1] == type) {
index = i;
@@ -266,8 +284,8 @@ module.controller('CategoryTreeCtrl', ['$scope', '$timeout', '$stateParams', '$w
});
}]);
module.controller('FeedDetailsCtrl', ['$scope', '$state', '$stateParams', 'FeedService', 'CategoryService', 'ProfileService', '$dialog',
function($scope, $state, $stateParams, FeedService, CategoryService, ProfileService, $dialog) {
module.controller('FeedDetailsCtrl', ['$scope', '$state', '$stateParams', 'FeedService', 'CategoryService', 'ProfileService',
function($scope, $state, $stateParams, FeedService, CategoryService, ProfileService) {
$scope.CategoryService = CategoryService;
$scope.user = ProfileService.get();
@@ -288,30 +306,15 @@ module.controller('FeedDetailsCtrl', ['$scope', '$state', '$stateParams', 'FeedS
$scope.unsubscribe = function() {
var sub = $scope.sub;
var title = 'Unsubscribe';
var msg = 'Unsubscribe from ' + sub.name + '?';
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') {
var data = {
id : sub.id
};
FeedService.unsubscribe(data, function() {
CategoryService.init();
});
$state.transitionTo('feeds.view', {
_id : 'all',
_type : 'category'
});
}
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',
'$dialog', function($scope, $state, $stateParams, FeedService, CategoryService, ProfileService, $dialog) {
function($scope, $state, $stateParams, FeedService, CategoryService, ProfileService) {
$scope.CategoryService = CategoryService;
$scope.user = ProfileService.get();
@@ -355,7 +358,7 @@ module.controller('CategoryDetailsCtrl', ['$scope', '$state', '$stateParams', 'F
};
return;
}
for ( var i = 0; i < CategoryService.flatCategories.length; i++) {
for (var i = 0; i < CategoryService.flatCategories.length; i++) {
var cat = CategoryService.flatCategories[i];
if (cat.id == $stateParams._id) {
$scope.category = {
@@ -380,29 +383,14 @@ module.controller('CategoryDetailsCtrl', ['$scope', '$state', '$stateParams', 'F
$scope.deleteCategory = function() {
var category = $scope.category;
var title = 'Delete category';
var msg = 'Delete category ' + category.name + ' ?';
var btns = [{
result : 'cancel',
label : 'Cancel'
}, {
result : 'ok',
label : 'OK',
cssClass : 'btn-primary'
}];
$dialog.messageBox(title, msg, btns).open().then(function(result) {
if (result == 'ok') {
CategoryService.remove({
id : category.id
}, function() {
CategoryService.init();
});
$state.transitionTo('feeds.view', {
_id : 'all',
_type : 'category'
});
}
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', [
'$scope',
'$http',
@@ -502,6 +505,10 @@ module.controller('ToolbarCtrl', [
markAll();
};
$scope.markAll12Hours = function() {
markAll(new Date().getTime() - 43200000);
};
$scope.markAllDay = function() {
markAll(new Date().getTime() - 86400000);
};
@@ -515,9 +522,10 @@ module.controller('ToolbarCtrl', [
};
$scope.search = function() {
$location.search('q', $scope.keywords);
var keywords = this.keywords;
$location.search('q', keywords);
$scope.$emit('emitEntrySearch', {
keywords : $scope.keywords
keywords : keywords
});
};
$scope.showButtons = function() {
@@ -570,7 +578,7 @@ module.controller('FeedSearchCtrl', ['$scope', '$state', '$filter', '$timeout',
}
var filtered = $scope.filtered;
for ( var i = 0; i < filtered.length; i++) {
for (var i = 0; i < filtered.length; i++) {
if ($scope.focus.id == filtered[i].id) {
index = i;
break;
@@ -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.busy = false;
$scope.hasMore = true;
@@ -714,7 +728,10 @@ module.controller('FeedListCtrl', [
$scope.busy = true;
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
}).length;
if ($scope.entries.length === 0) {
@@ -729,7 +746,7 @@ module.controller('FeedListCtrl', [
}
var callback = function(data) {
for ( var i = 0; i < data.entries.length; i++) {
for (var i = 0; i < data.entries.length; i++) {
var entry = data.entries[i];
if (!_.some($scope.entries, {
id : entry.id
@@ -746,15 +763,23 @@ module.controller('FeedListCtrl', [
$scope.feedLink = data.feedLink;
};
var service = $scope.selectedType == 'feed' ? FeedService : CategoryService;
service.entries({
var data = {
id : $scope.selectedId,
readType : $scope.keywords ? 'all' : $scope.settingsService.settings.readingMode,
order : $scope.settingsService.settings.readingOrder,
offset : offset,
limit : limit,
keywords : $scope.keywords
}, callback);
};
if ($scope.selectedType == 'feed') {
FeedService.entries(data, callback);
} else if ($scope.selectedType == 'category') {
CategoryService.entries(data, callback);
} else if ($scope.selectedType == 'tag') {
data.tag = data.id;
data.id = 'all';
CategoryService.entries(data, callback);
}
};
var watch_scrolling = true;
@@ -781,10 +806,11 @@ module.controller('FeedListCtrl', [
return;
} else {
var scrollTop = elemTop - $('#toolbar').outerHeight();
var speed = SettingsService.settings.scrollSpeed;
watch_scrolling = false;
$('html, body').animate({
scrollTop : scrollTop
}, 400, 'swing', function() {
}, speed, 'swing', function() {
watch_scrolling = true;
});
}
@@ -803,7 +829,7 @@ module.controller('FeedListCtrl', [
var docTop = w.scrollTop();
var current = null;
for ( var i = 0; i < $scope.entries.length; i++) {
for (var i = 0; i < $scope.entries.length; i++) {
var entry = $scope.entries[i];
var e = $('#entry_' + entry.id);
if (e.offset().top + e.height() > docTop + $('#toolbar').outerHeight()) {
@@ -866,7 +892,7 @@ module.controller('FeedListCtrl', [
$scope.markUpTo = function(entry) {
var entries = [];
for ( var i = 0; i < $scope.entries.length; i++) {
for (var i = 0; i < $scope.entries.length; i++) {
var e = $scope.entries[i];
if (!e.read) {
entries.push({
@@ -905,7 +931,7 @@ module.controller('FeedListCtrl', [
var getCurrentIndex = function() {
var index = -1;
if ($scope.current) {
for ( var i = 0; i < $scope.entries.length; i++) {
for (var i = 0; i < $scope.entries.length; i++) {
if ($scope.current == $scope.entries[i]) {
index = i;
break;
@@ -1194,6 +1220,7 @@ module.controller('FeedListCtrl', [
Mousetrap.bind('f', function(e) {
$('body').toggleClass('full-screen');
$('.main-content').css('margin-left', '');
return false;
});
@@ -1257,6 +1284,30 @@ module.controller('ManageUsersCtrl', ['$scope', '$state', '$location', 'AdminUse
multiSelect : false,
showColumnMenu : 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) {
$state.transitionTo('admin.useredit', {
@@ -1273,8 +1324,8 @@ module.controller('ManageUsersCtrl', ['$scope', '$state', '$location', 'AdminUse
};
}]);
module.controller('ManageUserCtrl', ['$scope', '$state', '$stateParams', '$dialog', 'AdminUsersService',
function($scope, $state, $stateParams, $dialog, AdminUsersService) {
module.controller('ManageUserCtrl', ['$scope', '$state', '$stateParams', 'AdminUsersService',
function($scope, $state, $stateParams, AdminUsersService) {
$scope.user = $stateParams._id ? AdminUsersService.get({
id : $stateParams._id
}) : {
@@ -1301,76 +1352,14 @@ module.controller('ManageUserCtrl', ['$scope', '$state', '$stateParams', '$dialo
}, alertFunction);
};
$scope.remove = function() {
var title = 'Delete user';
var msg = 'Delete user ' + $scope.user.name + ' ?';
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') {
AdminUsersService.remove({
id : $scope.user.id
}, function() {
$state.transitionTo('admin.userlist');
}, alertFunction);
}
});
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',
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',
function($scope, $location, $dialog, ProfileService, AnalyticsService) {
module.controller('ProfileCtrl', ['$scope', '$location', 'ProfileService', 'AnalyticsService',
function($scope, $location, ProfileService, AnalyticsService) {
AnalyticsService.track();
@@ -1423,23 +1412,8 @@ module.controller('ProfileCtrl', ['$scope', '$location', '$dialog', 'ProfileServ
});
};
$scope.deleteAccount = function() {
var title = 'Delete account';
var msg = 'Delete your acount? There\'s no turning back!';
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';
}
});
ProfileService.deleteAccount({});
window.location.href = 'logout';
};
}]);
@@ -1460,9 +1434,6 @@ module.controller('ManageSettingsCtrl', ['$scope', '$location', '$state', 'Admin
$scope.toUsers = function() {
$state.transitionTo('admin.userlist');
};
$scope.toCleanup = function() {
$state.transitionTo('admin.duplicate_feeds');
};
}]);
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 hostname = window.location.hostname;
$scope.subToMeUrl = baseUrl + 'rest/feed/subscribe?url={feed}';
$scope.subToMeName = hostname.indexOf('www.commafeed.com') !== -1 ? 'CommaFeed' : 'CommaFeed (' + hostname + ')';
var url = baseUrl + 'rest/feed/subscribe?url={feed}';
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
*/
@@ -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
*/
@@ -116,13 +164,14 @@ module.directive('category', [function() {
selectedId : '=',
showLabel : '=',
showChildren : '=',
unreadCount : '&'
unreadCount : '&',
tag : '='
},
restrict : 'E',
replace : true,
templateUrl : 'templates/_category.html',
controller : ['$scope', '$state', '$dialog', 'FeedService', 'CategoryService', 'SettingsService', 'MobileService',
function($scope, $state, $dialog, FeedService, CategoryService, SettingsService, MobileService) {
controller : ['$scope', '$state', 'FeedService', 'CategoryService', 'SettingsService', 'MobileService',
function($scope, $state, FeedService, CategoryService, SettingsService, MobileService) {
$scope.settingsService = SettingsService;
$scope.getClass = function(level) {
@@ -174,13 +223,14 @@ module.directive('category', [function() {
}
};
$scope.categoryClicked = function(id) {
$scope.categoryClicked = function(id, isTag) {
MobileService.toggleLeftMenu();
if ($scope.selectedType == 'category' && id == $scope.selectedId) {
var type = isTag ? 'tag' : 'category';
if ($scope.selectedType == type && id == $scope.selectedId) {
$scope.$emit('emitReload');
} else {
$state.transitionTo('feeds.view', {
_type : 'category',
_type : type,
_id : id
});
}
@@ -192,10 +242,16 @@ module.directive('category', [function() {
});
};
$scope.showCategoryDetails = function(category) {
$state.transitionTo('feeds.category_details', {
_id : category.id
});
$scope.showCategoryDetails = function(id, isTag) {
if (isTag) {
$state.transitionTo('feeds.tag_details', {
_id : id
});
} else {
$state.transitionTo('feeds.category_details', {
_id : id
});
}
};
$scope.toggleCategory = function(category, event) {

View File

@@ -1,5 +1,8 @@
var module = angular.module('commafeed.filters', []);
/**
* smart date formatter
*/
module.filter('entryDate', function() {
return function(timestamp, defaultValue) {
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() {
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() {
return function(html, keywords) {
if (keywords) {
@@ -39,7 +84,7 @@ module.filter('highlight', function() {
};
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);
}
}

View File

@@ -1,10 +1,10 @@
var app = angular.module('commafeed', ['ui', 'ui.bootstrap', 'ui.state', 'commafeed.directives', 'commafeed.controllers',
'commafeed.services', 'commafeed.filters', 'ngSanitize', 'infinite-scroll', 'ngGrid']);
var app = angular.module('commafeed', ['ngRoute', 'ngTouch', 'ui.utils', 'ui.bootstrap', 'ui.router', 'ui.select2', 'commafeed.directives',
'commafeed.controllers', 'commafeed.services', 'commafeed.filters', 'ngSanitize', 'infinite-scroll', 'ngGrid']);
app.config(['$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 success = function(response) {
@@ -39,6 +39,21 @@ app.config(['$routeProvider', '$stateProvider', '$urlRouterProvider', '$httpProv
templateUrl : 'templates/feeds.view.html',
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', {
url : '/search/:_keywords',
templateUrl : 'templates/feeds.view.html',
@@ -54,6 +69,11 @@ app.config(['$routeProvider', '$stateProvider', '$urlRouterProvider', '$httpProv
templateUrl : 'templates/feeds.category_details.html',
controller : 'CategoryDetailsCtrl'
});
$stateProvider.state('feeds.tag_details', {
url : '/details/tag/:_id',
templateUrl : 'templates/feeds.tag_details.html',
controller : 'TagDetailsCtrl'
});
$stateProvider.state('feeds.help', {
url : '/help',
templateUrl : 'templates/feeds.help.html',
@@ -90,11 +110,6 @@ app.config(['$routeProvider', '$stateProvider', '$urlRouterProvider', '$httpProv
templateUrl : 'templates/admin.useredit.html',
controller : 'ManageUserCtrl'
});
$stateProvider.state('admin.duplicate_feeds', {
url : '/feeds/duplicates',
templateUrl : 'templates/admin.duplicate_feeds.html',
controller : 'ManageDuplicateFeedsCtrl'
});
$stateProvider.state('admin.settings', {
url : '/settings',
templateUrl : 'templates/admin.settings.html',
@@ -105,7 +120,7 @@ app.config(['$routeProvider', '$stateProvider', '$urlRouterProvider', '$httpProv
templateUrl : 'templates/admin.metrics.html',
controller : 'MetricsCtrl'
});
$urlRouterProvider.when('/', '/feeds/view/category/all');
$urlRouterProvider.when('/admin', '/admin/settings');
$urlRouterProvider.otherwise('/');

View File

@@ -23,8 +23,7 @@ module.service('MobileService', ['$state', function($state) {
this.rightMenu = !this.rightMenu;
$('body').toggleClass('right-menu-active');
};
var width = (window.innerWidth > 0) ? window.innerWidth : screen.width;
this.mobile = width < 979;
this.mobile = device.mobile() || device.tablet();
}]);
module.factory('ProfileService', ['$resource', function($resource) {
@@ -126,7 +125,7 @@ module.factory('CategoryService', ['$resource', '$http', function($resource, $ht
callback(category, parentName);
var children = category.children;
if (children) {
for ( var c = 0; c < children.length; c++) {
for (var c = 0; c < children.length; c++) {
traverse(callback, children[c], category.name);
}
}
@@ -271,9 +270,29 @@ module.factory('EntryService', ['$resource', '$http', function($resource, $http)
params : {
_method : 'star'
}
},
tag : {
method : 'POST',
params : {
_method : 'tag'
}
}
};
var res = $resource('rest/entry/:_method', {}, actions);
res.tags = [];
var initTags = function() {
$http.get('rest/entry/tags').success(function(data) {
res.tags = [];
res.tags.push.apply(res.tags, data);
});
};
var oldTag = res.tag;
res.tag = function(data) {
oldTag(data, function() {
initTags();
});
};
initTags();
return res;
}]);
@@ -296,27 +315,6 @@ module.factory('AdminMetricsService', ['$resource', function($resource) {
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) {
var res = $resource('rest/server/get');
return res;

View File

@@ -1,3 +1,8 @@
.main-content {
margin-left: 250px;
padding-left: 15px;
}
.full-screen .main-content {
width: 100%;
margin-left: 0;
@@ -14,6 +19,7 @@
.entrylist-header h3 {
margin: 0;
line-height: 40px;
}
.entrylist-header a {
@@ -70,7 +76,7 @@
#feed-accordion .entry-heading-link {
color: black;
height: 20px;
height: 32px;
display: block;
cursor: pointer;
padding: 6px 0px;
@@ -143,7 +149,7 @@
}
.full-screen #feed-accordion .entry-body-content {
max-width: 100%;
max-width: 100%;
}
#feed-accordion .entry-enclosure {
@@ -172,6 +178,24 @@
text-decoration: none;
}
#feed-accordion .tags-panel {
margin-left: 30px;
}
#feed-accordion .tags-panel .label{
margin-left: 5px;
}
.select2-container-multi .select2-choices .select2-search-field input {
padding: 2px
}
#feed-accordion .tag-input {
margin: 0 0 0 5px;
padding: 0;
width: 200px;
}
#feed-accordion .entry-buttons label {
margin-bottom: 0px;
font-size: 12px;

View File

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

View File

@@ -1,4 +1,3 @@
.toolbar {
padding-top: 10px;
padding-bottom: 10px;
@@ -12,3 +11,6 @@
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 {
cursor: pointer;
}
.nolink {
color: inherit;
}
.nolink:hover {
text-decoration: none;
color: inherit;
}
.block {
display: block;
}
@@ -41,6 +70,7 @@
.welcome .preview {
margin: 20px 0 20px 0;
max-width: 100%;
}
.welcome .demo {

View File

@@ -1,16 +1,25 @@
@media ( max-width : 979px) {
html.mobile, html.tablet {
body {
padding-left: 0px;
padding-right: 0px;
}
.container-full {
padding-left: 5px;
padding-right: 5px;
}
.container-fluid {
padding-left: 0;
padding-right: 0;
.row {
margin-right: 0;
margin-left: 0;
}
.left-menu {
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;
}
.main-content {
@@ -18,6 +27,8 @@
float: none !important;
width: 100% !important;
margin-left: 0 !important;
padding-left: 0;
padding-right: 0;
}
#feed-accordion .entry-heading .shrink {
margin-left: 0;
@@ -30,16 +41,27 @@
margin-top: 22px;
}
#feed-accordion .entry-external-link {
right: 10px;
right: 15px;
margin-top: -24px;
}
#feed-accordion .entry-heading-link {
height: 40px;
height: 52px;
}
#feed-accordion .entry-heading .entry-date {
right: 35px;
right: 40px;
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 {
display: block !important;
width: 100%;
@@ -55,10 +77,10 @@
margin-top: 5px;
margin-left: 0;
}
body.right-menu-active .toolbar .actions .visible-desktop {
body.right-menu-active .toolbar .actions {
display: inherit !important;
}
#uvTab {
display: none;
}
}
}

View File

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

View File

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

View File

@@ -1,3 +1,3 @@
<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>

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