mirror of
https://github.com/Athou/commafeed.git
synced 2026-03-21 21:37:29 +00:00
Compare commits
342 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fca6132a0 | ||
|
|
137eba33c9 | ||
|
|
143699c0a4 | ||
|
|
0485403fff | ||
|
|
489fcb9666 | ||
|
|
7cc3b84ebc | ||
|
|
cb254f87d4 | ||
|
|
d4db98fd64 | ||
|
|
d14a6d8311 | ||
|
|
286c115167 | ||
|
|
6038b9e052 | ||
|
|
552082a36a | ||
|
|
5cea92d96d | ||
|
|
02b7b89b94 | ||
|
|
93697cf1f5 | ||
|
|
8daaee28c3 | ||
|
|
c32f608ec5 | ||
|
|
7b09029c5b | ||
|
|
6e1c414c84 | ||
|
|
e57976be99 | ||
|
|
a37e6a3f4c | ||
|
|
2dbe4064b2 | ||
|
|
2b0c0d467a | ||
|
|
40fa4516df | ||
|
|
5201c0cd14 | ||
|
|
61039dcd7e | ||
|
|
039ff4ee41 | ||
|
|
b40349805f | ||
|
|
d709d119ac | ||
|
|
8d2b6bdc12 | ||
|
|
ff78af2d56 | ||
|
|
ada53dba3b | ||
|
|
ba2f6c0f66 | ||
|
|
268869345c | ||
|
|
4b556bd3a9 | ||
|
|
6f10d35a4c | ||
|
|
33167fcdce | ||
|
|
e9c85b0e77 | ||
|
|
e521254600 | ||
|
|
a773d98400 | ||
|
|
ae066d3cd9 | ||
|
|
b5726fc0f3 | ||
|
|
4a056a0d27 | ||
|
|
7817431bce | ||
|
|
c02d2745c3 | ||
|
|
ee610ec800 | ||
|
|
6c0d585fef | ||
|
|
29417005b0 | ||
|
|
cf87fd8340 | ||
|
|
f1b85b0dde | ||
|
|
abef73d384 | ||
|
|
535f947f88 | ||
|
|
f27e243cc4 | ||
|
|
6a699ed5f1 | ||
|
|
9c1f5efab5 | ||
|
|
6b7ce56f6b | ||
|
|
b76ee4a2d0 | ||
|
|
b444a74a44 | ||
|
|
d43820cc82 | ||
|
|
e74e8fe1c2 | ||
|
|
9eb6e8ec27 | ||
|
|
fae94d3696 | ||
|
|
68e5ed64c9 | ||
|
|
f912d3b8bd | ||
|
|
fc03d2ee91 | ||
|
|
523b2b8db4 | ||
|
|
d547e9b6d7 | ||
|
|
71efc9f854 | ||
|
|
4f289f7467 | ||
|
|
02ef8bee71 | ||
|
|
ff5c1b00d7 | ||
|
|
30264be311 | ||
|
|
8ea44ab8c7 | ||
|
|
1b8ff7ca61 | ||
|
|
f00a066c22 | ||
|
|
859cf468aa | ||
|
|
5b486a917b | ||
|
|
9ace6b70f0 | ||
|
|
447029ae70 | ||
|
|
83f26cde53 | ||
|
|
8ac52690fd | ||
|
|
6934b2bd27 | ||
|
|
6647e4fcd4 | ||
|
|
21710f55f3 | ||
|
|
27bd9a7489 | ||
|
|
630d37125c | ||
|
|
9424237534 | ||
|
|
cba3fbeb5f | ||
|
|
58778ccf43 | ||
|
|
6c61d47d78 | ||
|
|
35e02f9d98 | ||
|
|
58c1650863 | ||
|
|
9b14ffa14c | ||
|
|
96c09bf4cd | ||
|
|
737cec744a | ||
|
|
13ed92bb94 | ||
|
|
076594c78e | ||
|
|
b6b1b4ebbe | ||
|
|
4007f37492 | ||
|
|
532d671feb | ||
|
|
fed7a1ac84 | ||
|
|
ddfd170ea8 | ||
|
|
bae5c67dfa | ||
|
|
84f51603fb | ||
|
|
f73ddc03e9 | ||
|
|
a16d9877cc | ||
|
|
c24e9e083c | ||
|
|
101602c6f6 | ||
|
|
18a7bd1fd1 | ||
|
|
dfbd556bb8 | ||
|
|
040cdde8ba | ||
|
|
06373480ae | ||
|
|
5713a78f2e | ||
|
|
b9f2f17a24 | ||
|
|
9adc993472 | ||
|
|
dcd5f3d529 | ||
|
|
18e70a0e6b | ||
|
|
5ad57d1608 | ||
|
|
74eaf48ceb | ||
|
|
30bb0cb291 | ||
|
|
b50e6b93bd | ||
|
|
a0b5a1462d | ||
|
|
4910f93c94 | ||
|
|
4a52bd0cb7 | ||
|
|
b0bfb73952 | ||
|
|
69d049a69a | ||
|
|
7d75153362 | ||
|
|
748bfa31ae | ||
|
|
e7d995edbc | ||
|
|
a144fb2e48 | ||
|
|
7521013e11 | ||
|
|
c6321fc6b2 | ||
|
|
7d92d5d096 | ||
|
|
ab201d5016 | ||
|
|
efa38d5ee9 | ||
|
|
e8769d09a8 | ||
|
|
a216444825 | ||
|
|
fee3e10e6b | ||
|
|
4d71a8f3c2 | ||
|
|
fc104b0b01 | ||
|
|
3dcb351b36 | ||
|
|
600d05d08f | ||
|
|
6b6ff70ad3 | ||
|
|
891f660738 | ||
|
|
6901b9b728 | ||
|
|
c7f211a7f8 | ||
|
|
c48ea1152c | ||
|
|
f5d0eb94b4 | ||
|
|
cebeef04a0 | ||
|
|
3e77a83ca6 | ||
|
|
c872b335e7 | ||
|
|
cc1e173552 | ||
|
|
35e0567705 | ||
|
|
fb2add305e | ||
|
|
74d4c18c4c | ||
|
|
da3ce07485 | ||
|
|
c7ab179a9e | ||
|
|
6fd11fcd56 | ||
|
|
3966cf165b | ||
|
|
0b2ada5d1c | ||
|
|
4278101bbe | ||
|
|
8b43af49fc | ||
|
|
6e29e8426b | ||
|
|
af11d3c771 | ||
|
|
e5c5af4d57 | ||
|
|
3dbdf5adf2 | ||
|
|
4d7a030b70 | ||
|
|
3351262dd7 | ||
|
|
5ec4377502 | ||
|
|
9c8402c3a5 | ||
|
|
928a45e48e | ||
|
|
1d088c5eae | ||
|
|
cdcf81ab7c | ||
|
|
9f196bafe9 | ||
|
|
5c9e1406a1 | ||
|
|
0b42e00b29 | ||
|
|
88b98a138f | ||
|
|
136c37885d | ||
|
|
812988b31a | ||
|
|
191680a01b | ||
|
|
467d1a754d | ||
|
|
d1973922cd | ||
|
|
3b7689975d | ||
|
|
3386a71c5e | ||
|
|
7bb65a5e76 | ||
|
|
f3a9c8e0e2 | ||
|
|
22861ca8d0 | ||
|
|
19118ea241 | ||
|
|
4a9dc7249f | ||
|
|
5dad9c2eb8 | ||
|
|
d6b35b00b9 | ||
|
|
fda8ab500b | ||
|
|
66df421de2 | ||
|
|
33c62f08ca | ||
|
|
b660602809 | ||
|
|
6dfce2ca30 | ||
|
|
655e20e99e | ||
|
|
f2b80bdc08 | ||
|
|
10af873fa5 | ||
|
|
d87a5b14f8 | ||
|
|
b87a18b993 | ||
|
|
c4185034e4 | ||
|
|
9d64426b00 | ||
|
|
c81cc8bea4 | ||
|
|
90e680d6be | ||
|
|
04c0833111 | ||
|
|
06151eab3b | ||
|
|
3dcb8590f6 | ||
|
|
a9b313aa4a | ||
|
|
1f2e35060b | ||
|
|
a96862fffa | ||
|
|
68cb8e194d | ||
|
|
c164926c54 | ||
|
|
de7516116d | ||
|
|
fccfe5b088 | ||
|
|
23aa5fa0a3 | ||
|
|
d384c0a141 | ||
|
|
18058c2a36 | ||
|
|
71727202f3 | ||
|
|
eee0b949de | ||
|
|
3cbbb67b0c | ||
|
|
7879f66e78 | ||
|
|
c14ac37495 | ||
|
|
73a77183aa | ||
|
|
09cfa21091 | ||
|
|
c193571ece | ||
|
|
04bc92b071 | ||
|
|
94e58a449c | ||
|
|
9d044195aa | ||
|
|
caff34cc3b | ||
|
|
34c5c0b1f7 | ||
|
|
906801e13c | ||
|
|
dad4c6b866 | ||
|
|
090462022f | ||
|
|
cbf9f65fb4 | ||
|
|
5a493cd55d | ||
|
|
dfc204ef05 | ||
|
|
56c6e2d29c | ||
|
|
db03dd12a0 | ||
|
|
6c67e6363a | ||
|
|
e2888beb4c | ||
|
|
bba9166885 | ||
|
|
504e4eab3e | ||
|
|
2e475c35cc | ||
|
|
ccf18758fb | ||
|
|
68f9852790 | ||
|
|
d0150de003 | ||
|
|
e2b792335b | ||
|
|
ece38c9e59 | ||
|
|
a19b5090bf | ||
|
|
e4b3c35892 | ||
|
|
4b229a759a | ||
|
|
1e9e42ac48 | ||
|
|
245a48f66e | ||
|
|
e6d8397550 | ||
|
|
d59bd43846 | ||
|
|
c1579c83c7 | ||
|
|
4d782e60ad | ||
|
|
c702f47927 | ||
|
|
9110cfd923 | ||
|
|
e40dd14bbf | ||
|
|
90aaae9959 | ||
|
|
e81dda0fa8 | ||
|
|
f93796d036 | ||
|
|
d06359cb81 | ||
|
|
8b68fb578f | ||
|
|
cca300e419 | ||
|
|
77c3ec0bbe | ||
|
|
ed81fc576a | ||
|
|
435fcb9669 | ||
|
|
9020d95b62 | ||
|
|
84d7a501d4 | ||
|
|
e65dd49d69 | ||
|
|
a705cbe6c2 | ||
|
|
60b8af3860 | ||
|
|
9ac4187aa8 | ||
|
|
6419d29489 | ||
|
|
4684e43f42 | ||
|
|
a477c9fa6d | ||
|
|
d1be331f99 | ||
|
|
cbc792d406 | ||
|
|
0313c5c560 | ||
|
|
18aa2fcd92 | ||
|
|
10461941d7 | ||
|
|
e6050219bc | ||
|
|
81481c37fe | ||
|
|
5ea92a7d18 | ||
|
|
f40630aced | ||
|
|
81850acdfe | ||
|
|
6819d5aa8b | ||
|
|
2aef4e5d05 | ||
|
|
6d4d2c3e7e | ||
|
|
87bcaa4731 | ||
|
|
5d2378f291 | ||
|
|
253507d14b | ||
|
|
548fb7099b | ||
|
|
0dd7c777ee | ||
|
|
6812bf2388 | ||
|
|
12bcbfa9f7 | ||
|
|
b5dfd371d9 | ||
|
|
e09d7fb103 | ||
|
|
0fe3afe254 | ||
|
|
db50d50c19 | ||
|
|
691bdb1512 | ||
|
|
d50b712bca | ||
|
|
3b68e4f32b | ||
|
|
259b9a90dd | ||
|
|
f4c5fd7eb4 | ||
|
|
3cd42d03f0 | ||
|
|
3497b82e8c | ||
|
|
15a24e4e75 | ||
|
|
96837f908e | ||
|
|
4ea5ebbf9e | ||
|
|
281e015376 | ||
|
|
5825a16aff | ||
|
|
2586a8c433 | ||
|
|
9f7c9c3428 | ||
|
|
9790ba735b | ||
|
|
e3dbcac9fb | ||
|
|
1c99929429 | ||
|
|
9b2cdbbb18 | ||
|
|
928cf1220e | ||
|
|
c0557856a3 | ||
|
|
97c2cc3d15 | ||
|
|
a94ef980bb | ||
|
|
eea0c24d2b | ||
|
|
c8fded3c56 | ||
|
|
8f2ba5e186 | ||
|
|
5ce2823d0b | ||
|
|
a0c70d326f | ||
|
|
5f28fd4114 | ||
|
|
7151db0909 | ||
|
|
e82888f8f3 | ||
|
|
4fb60a6ec6 | ||
|
|
27f22f6094 | ||
|
|
7497a0151a | ||
|
|
41f133afb1 | ||
|
|
4b15ecbc1b | ||
|
|
6498130850 | ||
|
|
24bd1121af | ||
|
|
3cccf741d6 | ||
|
|
0a2d2c3f43 |
@@ -1,6 +1,23 @@
|
||||
#!/bin/bash
|
||||
cd $OPENSHIFT_REPO_DIR
|
||||
if [ ! -d $OPENSHIFT_DATA_DIR/jdk1.8.0_20 ]
|
||||
then
|
||||
cd $OPENSHIFT_DATA_DIR
|
||||
wget http://www.java.net/download/jdk8u20/archive/b17/binaries/jdk-8u20-ea-bin-b17-linux-x64-04_jun_2014.tar.gz
|
||||
tar xvf *.tar.gz
|
||||
rm -f *.tar.gz
|
||||
fi
|
||||
if [ ! -d $OPENSHIFT_DATA_DIR/apache-maven-3.2.3 ]
|
||||
then
|
||||
cd $OPENSHIFT_DATA_DIR
|
||||
wget http://archive.apache.org/dist/maven/maven-3/3.2.3/binaries/apache-maven-3.2.3-bin.tar.gz
|
||||
tar xvf *.tar.gz
|
||||
rm -f *.tar.gz
|
||||
fi
|
||||
export M2=$OPENSHIFT_DATA_DIR/apache-maven-3.2.3/bin
|
||||
export JAVA_HOME=$OPENSHIFT_DATA_DIR/jdk1.8.0_20
|
||||
export PATH=$JAVA_HOME/bin:$M2:$PATH
|
||||
|
||||
cd $OPENSHIFT_REPO_DIR
|
||||
rm -rf $OPENSHIFT_REPO_DIR/node
|
||||
rm -rf $OPENSHIFT_REPO_DIR/node_modules
|
||||
rm -rf $OPENSHIFT_TMP_DIR/npm
|
||||
@@ -16,7 +33,4 @@ export HOME="$OPENSHIFT_TMP_DIR/local"
|
||||
|
||||
export NPM_CONFIG_ARCH="x64"
|
||||
|
||||
npm install npm
|
||||
export PATH="$OPENSHIFT_REPO_DIR/node_modules/.bin:$PATH"
|
||||
|
||||
mvn clean package -DskipTests -Dos.arch=x64
|
||||
mvn clean package -DskipTests -Dos.arch=x64 -s .openshift/settings.xml
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/bin/bash
|
||||
cd $OPENSHIFT_REPO_DIR
|
||||
nohup java -jar target/commafeed.jar server .openshift/config.mysql.yml > ${OPENSHIFT_DIY_LOG_DIR}/commafeed.log 2>&1 &
|
||||
export JAVA_HOME=$OPENSHIFT_DATA_DIR/jdk1.8.0_20
|
||||
nohup $JAVA_HOME/bin/java -jar target/commafeed.jar server .openshift/config.mysql.yml > ${OPENSHIFT_DIY_LOG_DIR}/commafeed.log 2>&1 &
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
# CommaFeed settings
|
||||
# ------------------
|
||||
app:
|
||||
# context path of the application
|
||||
contextPath: /
|
||||
|
||||
# url used to access commafeed
|
||||
publicUrl: https://@OPENSHIFT_APP_DNS@/
|
||||
|
||||
# wether to allow user registrations
|
||||
allowRegistrations: false
|
||||
|
||||
# create a demo account the first time the app starts
|
||||
createDemoAccount: false
|
||||
|
||||
# put your google analytics tracking code here
|
||||
googleAnalyticsTrackingCode:
|
||||
|
||||
# put your google server key (used for youtube favicon fetching)
|
||||
googleAuthKey:
|
||||
|
||||
# number of http threads
|
||||
backgroundThreads: 3
|
||||
|
||||
@@ -47,6 +50,9 @@ app:
|
||||
# time to keep unread statuses (in days), 0 to disable
|
||||
keepStatusDays: 0
|
||||
|
||||
# entries to keep per feed, old entries will be deleted, 0 to disable
|
||||
maxFeedCapacity: 500
|
||||
|
||||
# cache service to use, possible values are 'noop' and 'redis'
|
||||
cache: noop
|
||||
|
||||
|
||||
0
.openshift/markers/java8
Normal file
0
.openshift/markers/java8
Normal file
@@ -1,3 +1,41 @@
|
||||
<settings>
|
||||
<localRepository>$OPENSHIFT_DATA_DIR</localRepository>
|
||||
<mirrors>
|
||||
<mirror>
|
||||
<id>nexus</id>
|
||||
<mirrorOf>central</mirrorOf>
|
||||
<url>http://mirror1.ops.rhcloud.com/nexus/content/groups/public</url>
|
||||
</mirror>
|
||||
</mirrors>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>nexus</id>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>central</id>
|
||||
<url>http://central</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>central</id>
|
||||
<url>http://central</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
</profile>
|
||||
</profiles>
|
||||
<activeProfiles>
|
||||
<activeProfile>nexus</activeProfile>
|
||||
</activeProfiles>
|
||||
</settings>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
language: java
|
||||
jdk:
|
||||
- openjdk7
|
||||
- oraclejdk7
|
||||
- oraclejdk8
|
||||
28
CHANGELOG
28
CHANGELOG
@@ -1,3 +1,31 @@
|
||||
v 2.4.0
|
||||
- users were not able to change password or delete account
|
||||
- fix api key generation
|
||||
- feed entries can now be sorted alphabetically
|
||||
- fix facebook sharing
|
||||
- fix layout on iOS
|
||||
- postgresql driver update (fix for postgres 9.6)
|
||||
- various internationalization fixes
|
||||
- security fixes
|
||||
v 2.3.0
|
||||
- dropwizard upgrade 0.9.1
|
||||
- feed enclosures are hidden if they already displayed in the content
|
||||
- fix youtube favicons
|
||||
- various internationalization fixes
|
||||
v 2.2.0
|
||||
- fix youtube and instagram favicon fetching
|
||||
- mark as read filter was lost when a feed was rearranged with drag&drop
|
||||
- feed entry categories are now displayed if available
|
||||
- various performance and dependencies upgrades
|
||||
- java8 is now required
|
||||
v 2.1.0
|
||||
- dropwizard upgrade to 0.8.0
|
||||
- you have to remove the "app.contextPath" setting from your yml file, you can optionally use server.applicationContextPath instead
|
||||
- new setting app.maxFeedCapacity for deleting old entries
|
||||
- ability to set filtering expressions for subscriptions to automatically mark new entries as read based on title, content, author or url.
|
||||
- ability to use !keyword or -keyword to exclude a keyword from a search query
|
||||
- facebook feeds now show user favicon instead of facebook favicon
|
||||
- new dark theme 'nightsky'
|
||||
v 2.0.3
|
||||
- internet explorer ajax cache workaround
|
||||
- categories are now deletable again
|
||||
|
||||
81
README.md
81
README.md
@@ -1,34 +1,56 @@
|
||||
CommaFeed [](https://travis-ci.org/Athou/commafeed)
|
||||
=========
|
||||
# CommaFeed [](https://travis-ci.org/Athou/commafeed)
|
||||
|
||||
Sources for [CommaFeed.com](http://www.commafeed.com/).
|
||||
|
||||
Google Reader inspired self-hosted RSS reader, based on Dropwizard and AngularJS.
|
||||
CommaFeed is now considered feature-complete and is in maintenance mode.
|
||||
|
||||
Related open-source projects
|
||||
----------------------------
|
||||
## Related open-source projects
|
||||
|
||||
Android apps: [News+ extension](https://github.com/Athou/commafeed-newsplus) - [Android app](https://github.com/doomrobo/CommaFeed-Android-Reader)
|
||||
|
||||
Android apps: [News+ extension](https://github.com/Athou/commafeed-newsplus)
|
||||
|
||||
Browser extensions: [Chrome](https://github.com/Athou/commafeed-chrome) - [Firefox](https://github.com/Athou/commafeed-firefox) - [Opera](https://github.com/Athou/commafeed-opera) - [Safari](https://github.com/Athou/commafeed-safari)
|
||||
|
||||
Deployment on your own server
|
||||
-----------------------------
|
||||
## Deployment on your own server
|
||||
|
||||
### The very short version (download precompiled package)
|
||||
|
||||
mkdir commafeed && cd commafeed
|
||||
wget https://github.com/Athou/commafeed/releases/download/2.3.0/commafeed.jar
|
||||
wget https://raw.githubusercontent.com/Athou/commafeed/2.3.0/config.yml.example -O config.yml
|
||||
vi config.yml
|
||||
java -Djava.net.preferIPv4Stack=true -jar commafeed.jar server config.yml
|
||||
|
||||
### The short version (build from sources)
|
||||
|
||||
git clone https://github.com/Athou/commafeed.git
|
||||
cd commafeed
|
||||
./mvnw clean package
|
||||
cp config.yml.example config.yml
|
||||
vi config.yml
|
||||
java -Djava.net.preferIPv4Stack=true -jar target/commafeed.jar server config.yml
|
||||
|
||||
### The long version (same as the short version, but more detailed)
|
||||
|
||||
CommaFeed 2.0 has been rewritten to use Dropwizard and gulp instead of using tomee and wro4j. The latest version of the 1.x branch is available [here](https://github.com/Athou/commafeed/tree/1.x).
|
||||
|
||||
For storage, you can either use an embedded H2 database or an external MySQL, PostgreSQL or SQLServer database.
|
||||
You also need Maven 3.x (and a Java 1.7+ JDK) installed in order to build the application.
|
||||
For storage, you can either use an embedded H2 database (use it only to test CommaFeed) or an external MySQL, PostgreSQL or SQLServer database.
|
||||
You also need the Java 1.8+ JDK in order to build the application.
|
||||
|
||||
To install maven and openjdk on Ubuntu, issue the following commands
|
||||
To install the required packages to build CommaFeed on Ubuntu, issue the following commands
|
||||
|
||||
sudo apt-get install build-essential openjdk-7-jdk maven
|
||||
# Make sure java7 is the selected java version
|
||||
# if openjdk-8-jdk is not available on your ubuntu version (14.04 LTS), add the following repo first
|
||||
sudo add-apt-repository ppa:openjdk-r/ppa
|
||||
sudo apt-get update
|
||||
|
||||
sudo apt-get install g++ build-essential openjdk-8-jdk
|
||||
|
||||
# Make sure java8 is the selected java version
|
||||
sudo update-alternatives --config java
|
||||
sudo update-alternatives --config javac
|
||||
|
||||
|
||||
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.
|
||||
|
||||
Clone this repository. If you don't have git you can download the sources as a zip file from [here](https://github.com/Athou/commafeed/archive/master.zip)
|
||||
|
||||
git clone https://github.com/Athou/commafeed.git
|
||||
@@ -36,17 +58,16 @@ Clone this repository. If you don't have git you can download the sources as a z
|
||||
|
||||
Now build the application
|
||||
|
||||
mvn clean package
|
||||
./mvnw clean package
|
||||
|
||||
Copy `config.yml.example` to `config.yml` then edit the file to your liking.
|
||||
Issue the following command to run the app, the server will listen by default on `http://localhost:8082`. The default user is `admin` and the default password is `admin`.
|
||||
|
||||
java -jar target/commafeed.jar server config.yml
|
||||
java -Djava.net.preferIPv4Stack=true -jar target/commafeed.jar server config.yml
|
||||
|
||||
You can use a proxy http server such as nginx or apache.
|
||||
|
||||
Deployment on OpenShift
|
||||
-----------------------------
|
||||
## Deployment on OpenShift
|
||||
|
||||
[OpenShift](https://openshift.redhat.com) is Red Hat's Platform-as-a-Service (PaaS) that allows developers to quickly develop, host, and scale applications in a cloud environment. CommaFeed runs perfectly on OpenShift and can even be used in the free tier. Follow the [Getting Started](https://developers.openshift.com/en/getting-started-overview.html) guide and after you sign up and install the Command Line Tools (RHC), do:
|
||||
|
||||
@@ -55,9 +76,12 @@ Deployment on OpenShift
|
||||
git remote add upstream -m master https://github.com/Athou/commafeed.git
|
||||
git pull -s recursive -X theirs upstream master
|
||||
git push
|
||||
|
||||
# To upgrade an existing openshift installation
|
||||
git pull upstream master
|
||||
git push
|
||||
|
||||
Translate CommaFeed into your language
|
||||
--------------------------------------
|
||||
## Translate CommaFeed into your language
|
||||
|
||||
Files for internationalization are located [here](https://github.com/Athou/commafeed/tree/master/src/main/app/i18n).
|
||||
|
||||
@@ -65,18 +89,16 @@ To add a new language, create a new file in that directory.
|
||||
The name of the file should be the two-letters [ISO-639-1 language code](http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes).
|
||||
The language has to be referenced in the `src/main/app/js/i18n.js` file to be picked up.
|
||||
|
||||
Themes
|
||||
---------------------
|
||||
## Themes
|
||||
|
||||
To create a theme, create a new file `src/main/webapp/sass/themes/_<theme>.scss`. Your styles should be wrapped in a `#theme-<theme>` element and use the [SCSS format](http://sass-lang.com/) which is a superset of CSS.
|
||||
To create a theme, create a new file `src/main/app/sass/themes/_<theme>.scss`. Your styles should be wrapped in a `#theme-<theme>` element and use the [SCSS format](http://sass-lang.com/) which is a superset of CSS.
|
||||
|
||||
Don't forget to reference your theme in `src/main/webapp/sass/app.scss` and in `src/main/webapp/js/controllers.js` (look for `$scope.themes`).
|
||||
Don't forget to reference your theme in `src/main/app/sass/app.scss` and in `src/main/app/js/controllers.js` (look for `$scope.themes`).
|
||||
|
||||
See [_test.scss](https://github.com/Athou/commafeed/blob/master/src/main/webapp/sass/themes/_test.scss) for an example.
|
||||
See [_test.scss](https://github.com/Athou/commafeed/blob/master/src/main/app/sass/themes/_test.scss) for an example.
|
||||
|
||||
|
||||
Local development
|
||||
-----------------
|
||||
## Local development
|
||||
|
||||
Steps to configuring a development environment for CommaFeed may include, but may not be limited to:
|
||||
|
||||
@@ -100,10 +122,9 @@ Steps to configuring a development environment for CommaFeed may include, but ma
|
||||
14. When you're done developing, create a fork at the top of https://github.com/Athou/CommaFeed page and commit your changes to it.
|
||||
15. If you'd like to contribute to CommaFeed, create a pull request from your repository to https://github.com/Athou/CommaFeed when your changes are ready. There's a button to do so at the top of https://github.com/Athou/CommaFeed.
|
||||
|
||||
Copyright and license
|
||||
---------------------
|
||||
## Copyright and license
|
||||
|
||||
Copyright 2013-2014 CommaFeed.
|
||||
Copyright 2013-2016 CommaFeed.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this work except in compliance with the License.
|
||||
|
||||
38
bower.json
38
bower.json
@@ -2,32 +2,36 @@
|
||||
"name": "commafeed",
|
||||
"version": "2.0.0",
|
||||
"dependencies": {
|
||||
"jquery": "1.11.0",
|
||||
"jquery": "2.1.3",
|
||||
"jquery-ui": "1.10.3",
|
||||
"jquery-mousewheel": "3.1.12",
|
||||
"lodash": "2.4.1",
|
||||
"bootstrap": "3.1.1",
|
||||
"lodash": "3.4.0",
|
||||
"bootstrap": "3.3.2",
|
||||
"font-awesome": "3.2.1",
|
||||
"angular": "1.2.16",
|
||||
"angular-resource": "1.2.16",
|
||||
"angular-route": "1.2.16",
|
||||
"angular-sanitize": "1.2.16",
|
||||
"angular-touch": "1.2.16",
|
||||
"angular-animate": "1.2.16",
|
||||
"angular-ui-router": "0.2.8",
|
||||
"angular": "1.3.14",
|
||||
"angular-resource": "1.3.14",
|
||||
"angular-route": "1.3.14",
|
||||
"angular-sanitize": "1.3.14",
|
||||
"angular-touch": "1.3.14",
|
||||
"angular-animate": "1.3.14",
|
||||
"angular-ui-router": "0.2.13",
|
||||
"angular-ui-utils": "0.1.0",
|
||||
"angular-ui-select2": "0.0.5",
|
||||
"angular-bootstrap": "0.2.0",
|
||||
"angular-loading-bar": "0.5.0",
|
||||
"angular-translate": "2.2.0",
|
||||
"angular-translate-loader-static-files": "2.2.0",
|
||||
"angular-loading-bar": "0.6.0",
|
||||
"angular-translate": "2.6.1",
|
||||
"angular-translate-loader-static-files": "2.6.1",
|
||||
"ngInfiniteScroll": "1.0.0",
|
||||
"ng-grid": "2.0.6",
|
||||
"mousetrap": "1.4.6",
|
||||
"momentjs": "2.6.0",
|
||||
"device.js": "matthewhudson/device.js#2ae5c775e35ccc837589e5af34e292c54936778c",
|
||||
"momentjs": "2.9.0",
|
||||
"devicejs": "0.2.4",
|
||||
"readabilicons": "arc90/readability-readabilicons#34c55561c5b8ec6e90714b50237c06b13cb9d59c",
|
||||
"zocial": "samcollins/css-social-buttons#1f59ecacde475e563fb6771667597493ec4eecb6",
|
||||
"swagger-ui": "2.0.21"
|
||||
"zocial-less": "1.0.0",
|
||||
"swagger-ui": "2.1.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"angular": "1.3.14",
|
||||
"angular-translate": "2.6.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
# CommaFeed settings
|
||||
# ------------------
|
||||
app:
|
||||
# context path of the application
|
||||
contextPath: /
|
||||
|
||||
# url used to access commafeed
|
||||
publicUrl: http://localhost:8082/
|
||||
|
||||
# wether to allow user registrations
|
||||
allowRegistrations: true
|
||||
|
||||
# create a demo account the first time the app starts
|
||||
createDemoAccount: false
|
||||
|
||||
# put your google analytics tracking code here
|
||||
googleAnalyticsTrackingCode:
|
||||
|
||||
# put your google server key (used for youtube favicon fetching)
|
||||
googleAuthKey:
|
||||
|
||||
# number of http threads
|
||||
backgroundThreads: 3
|
||||
|
||||
@@ -25,6 +28,14 @@ app:
|
||||
smtpTls: false
|
||||
smtpUserName: user
|
||||
smtpPassword: pass
|
||||
|
||||
# Graphite Metric settings
|
||||
# Allows those who use Graphite to have CommaFeed send metrics for graphing (time in seconds)
|
||||
graphiteEnabled: false
|
||||
graphitePrefix: "test.commafeed"
|
||||
graphiteHost: "localhost"
|
||||
graphitePort: 2003
|
||||
graphiteInterval: 60
|
||||
|
||||
# wether this commafeed instance has a lot of feeds to refresh
|
||||
# leave this to false in almost all cases
|
||||
@@ -47,6 +58,9 @@ app:
|
||||
# time to keep unread statuses (in days), 0 to disable
|
||||
keepStatusDays: 0
|
||||
|
||||
# entries to keep per feed, old entries will be deleted, 0 to disable
|
||||
maxFeedCapacity: 500
|
||||
|
||||
# cache service to use, possible values are 'noop' and 'redis'
|
||||
cache: noop
|
||||
|
||||
@@ -74,12 +88,7 @@ database:
|
||||
password: sa
|
||||
properties:
|
||||
charSet: UTF-8
|
||||
maxWaitForConnection: 1s
|
||||
validationQuery: "/* CommaFeed Health Check */ SELECT 1"
|
||||
minSize: 1
|
||||
maxSize: 50
|
||||
checkConnectionWhileIdle: true
|
||||
maxConnectionAge: 30m
|
||||
validationQuery: "/* CommaFeed Health Check */ SELECT 1"
|
||||
|
||||
server:
|
||||
applicationConnectors:
|
||||
@@ -94,7 +103,7 @@ logging:
|
||||
loggers:
|
||||
com.commafeed: DEBUG
|
||||
liquibase: INFO
|
||||
org.hibernate.SQL: ALL
|
||||
org.hibernate.SQL: INFO # or ALL for sql debugging
|
||||
org.hibernate.engine.internal.StatisticalLoggingSessionEventListener: WARN
|
||||
appenders:
|
||||
- type: console
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
# CommaFeed settings
|
||||
# ------------------
|
||||
app:
|
||||
# context path of the application
|
||||
contextPath: /
|
||||
|
||||
# url used to access commafeed
|
||||
publicUrl: http://localhost:8082/
|
||||
|
||||
# wether to allow user registrations
|
||||
allowRegistrations: false
|
||||
|
||||
# create a demo account the first time the app starts
|
||||
createDemoAccount: false
|
||||
|
||||
# put your google analytics tracking code here
|
||||
googleAnalyticsTrackingCode:
|
||||
|
||||
# put your google server key (used for youtube favicon fetching)
|
||||
googleAuthKey:
|
||||
|
||||
# number of http threads
|
||||
backgroundThreads: 3
|
||||
|
||||
@@ -25,6 +28,15 @@ app:
|
||||
smtpTls: false
|
||||
smtpUserName:
|
||||
smtpPassword:
|
||||
smtpFromAddress:
|
||||
|
||||
# Graphite Metric settings
|
||||
# Allows those who use Graphite to have CommaFeed send metrics for graphing (time in seconds)
|
||||
graphiteEnabled: false
|
||||
graphitePrefix: "test.commafeed"
|
||||
graphiteHost: "localhost"
|
||||
graphitePort: 2003
|
||||
graphiteInterval: 60
|
||||
|
||||
# wether this commafeed instance has a lot of feeds to refresh
|
||||
# leave this to false in almost all cases
|
||||
@@ -47,6 +59,9 @@ app:
|
||||
# time to keep unread statuses (in days), 0 to disable
|
||||
keepStatusDays: 0
|
||||
|
||||
# entries to keep per feed, old entries will be deleted, 0 to disable
|
||||
maxFeedCapacity: 500
|
||||
|
||||
# cache service to use, possible values are 'noop' and 'redis'
|
||||
cache: noop
|
||||
|
||||
@@ -69,17 +84,15 @@ app:
|
||||
|
||||
database:
|
||||
driverClass: org.h2.Driver
|
||||
url: jdbc:h2:/home/commafeed/db
|
||||
url: jdbc:h2:/home/commafeed/db;mv_store=false
|
||||
user: sa
|
||||
password: sa
|
||||
properties:
|
||||
charSet: UTF-8
|
||||
maxWaitForConnection: 1s
|
||||
validationQuery: "/* CommaFeed Health Check */ SELECT 1"
|
||||
minSize: 1
|
||||
maxSize: 50
|
||||
checkConnectionWhileIdle: true
|
||||
maxConnectionAge: 30m
|
||||
validationQuery: "/* CommaFeed Health Check */ SELECT 1"
|
||||
minSize: 1
|
||||
maxSize: 50
|
||||
maxConnectionAge: 30m
|
||||
|
||||
server:
|
||||
applicationConnectors:
|
||||
|
||||
31
gulpfile.js
31
gulpfile.js
@@ -4,7 +4,6 @@ var revReplace = require('gulp-rev-replace');
|
||||
var minifyCSS = require('gulp-minify-css');
|
||||
var uglify = require('gulp-uglify');
|
||||
var filter = require('gulp-filter');
|
||||
var bower = require('gulp-bower');
|
||||
var connect = require('gulp-connect');
|
||||
var modRewrite = require('connect-modrewrite');
|
||||
var sass = require('gulp-sass');
|
||||
@@ -15,10 +14,6 @@ var SRC_DIR = 'src/main/app/';
|
||||
var TEMP_DIR = 'target/gulp/'
|
||||
var BUILD_DIR = 'target/classes/assets/';
|
||||
|
||||
gulp.task('bower', function() {
|
||||
return bower();
|
||||
});
|
||||
|
||||
gulp.task('images', function() {
|
||||
return gulp.src(SRC_DIR + 'images/**/*').pipe(gulp.dest(BUILD_DIR + 'images'));
|
||||
});
|
||||
@@ -27,34 +22,36 @@ gulp.task('i18n', function() {
|
||||
return gulp.src(SRC_DIR + 'i18n/**/*.js').pipe(gulp.dest(BUILD_DIR + 'i18n'));
|
||||
});
|
||||
|
||||
gulp.task('favicons', function() {
|
||||
gulp.task('resources', function() {
|
||||
var favicons_png = SRC_DIR + '*.png';
|
||||
var favicons_ico = SRC_DIR + '*.ico';
|
||||
var favicons_svg = SRC_DIR + '*.svg';
|
||||
return gulp.src([favicons_png, favicons_ico, favicons_svg]).pipe(gulp.dest(BUILD_DIR));
|
||||
var manifest = SRC_DIR + 'manifest.json';
|
||||
return gulp.src([favicons_png, favicons_ico, favicons_svg, manifest]).pipe(gulp.dest(BUILD_DIR));
|
||||
});
|
||||
|
||||
gulp.task('sass', function() {
|
||||
return gulp.src(SRC_DIR + 'sass/app.scss').pipe(sass()).pipe(gulp.dest(TEMP_DIR + 'css'));
|
||||
});
|
||||
|
||||
gulp.task('fonts', ['bower'], function() {
|
||||
gulp.task('fonts', function() {
|
||||
var font_awesome = SRC_DIR + 'lib/font-awesome/font/fontawesome-webfont.*';
|
||||
var zocial = SRC_DIR + 'lib/zocial/css/zocial-regular-*';
|
||||
var zocial = SRC_DIR + 'lib/zocial-less/css/zocial-regular-*';
|
||||
var readabilicons = SRC_DIR + 'lib/readabilicons/webfont/fonts/readabilicons-*';
|
||||
return gulp.src([font_awesome, zocial, readabilicons]).pipe(gulp.dest(BUILD_DIR + 'font'));
|
||||
});
|
||||
|
||||
gulp.task('select2', ['bower'], function() {
|
||||
gulp.task('select2', function() {
|
||||
var gif = SRC_DIR + 'lib/select2/*.gif';
|
||||
var png = SRC_DIR + 'lib/select2/*.png';
|
||||
return gulp.src([gif, png]).pipe(gulp.dest(BUILD_DIR + 'css'));
|
||||
});
|
||||
|
||||
gulp.task('swagger-ui', ['bower'], function() {
|
||||
gulp.task('swagger-ui', function() {
|
||||
var index_html = SRC_DIR + 'api/index.html';
|
||||
var swagger_json = 'target/swagger/swagger.json';
|
||||
var lib = SRC_DIR + 'lib/swagger-ui/dist/**/*';
|
||||
return gulp.src([lib, index_html]).pipe(gulp.dest(BUILD_DIR + 'api'));
|
||||
return gulp.src([lib, index_html, swagger_json]).pipe(gulp.dest(BUILD_DIR + 'api'));
|
||||
});
|
||||
|
||||
gulp.task('template-cache', function() {
|
||||
@@ -65,17 +62,17 @@ gulp.task('template-cache', function() {
|
||||
return gulp.src(SRC_DIR + 'templates/**/*.html').pipe(templateCache(options)).pipe(gulp.dest(TEMP_DIR + 'js'));
|
||||
});
|
||||
|
||||
gulp.task('build-dev', ['images', 'i18n', 'favicons', 'sass', 'fonts', 'select2', 'swagger-ui', 'template-cache', 'bower'], function() {
|
||||
gulp.task('build-dev', ['images', 'i18n', 'resources', 'sass', 'fonts', 'select2', 'swagger-ui', 'template-cache'], function() {
|
||||
var assets = useref.assets({
|
||||
searchPath : [SRC_DIR, TEMP_DIR]
|
||||
});
|
||||
var jsFilter = filter("**/*.js");
|
||||
var cssFilter = filter("**/*.css");
|
||||
return gulp.src([SRC_DIR + 'index.html', TEMP_DIR + 'app.css']).pipe(assets).pipe(rev()).pipe(assets.restore()).pipe(useref()).pipe(
|
||||
revReplace()).pipe(gulp.dest(BUILD_DIR));
|
||||
revReplace()).pipe(gulp.dest(BUILD_DIR)).pipe(connect.reload());
|
||||
});
|
||||
|
||||
gulp.task('build', ['images', 'i18n', 'favicons', 'sass', 'fonts', 'select2', 'swagger-ui', 'template-cache', 'bower'], function() {
|
||||
gulp.task('build', ['images', 'i18n', 'resources', 'sass', 'fonts', 'select2', 'swagger-ui', 'template-cache'], function() {
|
||||
var assets = useref.assets({
|
||||
searchPath : [SRC_DIR, TEMP_DIR]
|
||||
});
|
||||
@@ -101,7 +98,9 @@ gulp.task('serve', function() {
|
||||
connect.server({
|
||||
root : BUILD_DIR,
|
||||
port : 8082,
|
||||
livereload : true,
|
||||
middleware : function() {
|
||||
var api = '^/api/(.*)$ http://localhost:8083/rest/$1 [P]';
|
||||
var rest = '^/rest/(.*)$ http://localhost:8083/rest/$1 [P]';
|
||||
var next = '^/next(.*)$ http://localhost:8083/next$1 [P]';
|
||||
var logout = '^/logout$ http://localhost:8083/logout [P]';
|
||||
@@ -113,4 +112,4 @@ gulp.task('serve', function() {
|
||||
});
|
||||
|
||||
gulp.task('dev', ['build-dev', 'watch', 'serve']);
|
||||
gulp.task('default', ['build']);
|
||||
gulp.task('default', ['build']);
|
||||
|
||||
BIN
maven/maven-wrapper.jar
Normal file
BIN
maven/maven-wrapper.jar
Normal file
Binary file not shown.
3
maven/maven-wrapper.properties
Normal file
3
maven/maven-wrapper.properties
Normal file
@@ -0,0 +1,3 @@
|
||||
#Maven download properties
|
||||
#Sat Jul 04 09:06:32 CEST 2015
|
||||
distributionUrl=https\://repository.apache.org/content/repositories/releases/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip
|
||||
234
mvnw
vendored
Executable file
234
mvnw
vendored
Executable file
@@ -0,0 +1,234 @@
|
||||
#!/bin/sh
|
||||
# ----------------------------------------------------------------------------
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Maven2 Start Up Batch script
|
||||
#
|
||||
# Required ENV vars:
|
||||
# ------------------
|
||||
# JAVA_HOME - location of a JDK home dir
|
||||
#
|
||||
# Optional ENV vars
|
||||
# -----------------
|
||||
# M2_HOME - location of maven2's installed home dir
|
||||
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||
# e.g. to debug Maven itself, use
|
||||
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
if [ -z "$MAVEN_SKIP_RC" ] ; then
|
||||
|
||||
if [ -f /etc/mavenrc ] ; then
|
||||
. /etc/mavenrc
|
||||
fi
|
||||
|
||||
if [ -f "$HOME/.mavenrc" ] ; then
|
||||
. "$HOME/.mavenrc"
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
# OS specific support. $var _must_ be set to either true or false.
|
||||
cygwin=false;
|
||||
darwin=false;
|
||||
mingw=false
|
||||
case "`uname`" in
|
||||
CYGWIN*) cygwin=true ;;
|
||||
MINGW*) mingw=true;;
|
||||
Darwin*) darwin=true
|
||||
#
|
||||
# Look for the Apple JDKs first to preserve the existing behaviour, and then look
|
||||
# for the new JDKs provided by Oracle.
|
||||
#
|
||||
if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then
|
||||
#
|
||||
# Apple JDKs
|
||||
#
|
||||
export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then
|
||||
#
|
||||
# Apple JDKs
|
||||
#
|
||||
export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then
|
||||
#
|
||||
# Oracle JDKs
|
||||
#
|
||||
export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then
|
||||
#
|
||||
# Apple JDKs
|
||||
#
|
||||
export JAVA_HOME=`/usr/libexec/java_home`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -z "$JAVA_HOME" ] ; then
|
||||
if [ -r /etc/gentoo-release ] ; then
|
||||
JAVA_HOME=`java-config --jre-home`
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$M2_HOME" ] ; then
|
||||
## resolve links - $0 may be a link to maven's home
|
||||
PRG="$0"
|
||||
|
||||
# need this for relative symlinks
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG="`dirname "$PRG"`/$link"
|
||||
fi
|
||||
done
|
||||
|
||||
saveddir=`pwd`
|
||||
|
||||
M2_HOME=`dirname "$PRG"`/..
|
||||
|
||||
# make it fully qualified
|
||||
M2_HOME=`cd "$M2_HOME" && pwd`
|
||||
|
||||
cd "$saveddir"
|
||||
# echo Using m2 at $M2_HOME
|
||||
fi
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched
|
||||
if $cygwin ; then
|
||||
[ -n "$M2_HOME" ] &&
|
||||
M2_HOME=`cygpath --unix "$M2_HOME"`
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
[ -n "$CLASSPATH" ] &&
|
||||
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
|
||||
fi
|
||||
|
||||
# For Migwn, ensure paths are in UNIX format before anything is touched
|
||||
if $mingw ; then
|
||||
[ -n "$M2_HOME" ] &&
|
||||
M2_HOME="`(cd "$M2_HOME"; pwd)`"
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
|
||||
# TODO classpath?
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
javaExecutable="`which javac`"
|
||||
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
|
||||
# readlink(1) is not available as standard on Solaris 10.
|
||||
readLink=`which readlink`
|
||||
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
|
||||
if $darwin ; then
|
||||
javaHome="`dirname \"$javaExecutable\"`"
|
||||
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
|
||||
else
|
||||
javaExecutable="`readlink -f \"$javaExecutable\"`"
|
||||
fi
|
||||
javaHome="`dirname \"$javaExecutable\"`"
|
||||
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
|
||||
JAVA_HOME="$javaHome"
|
||||
export JAVA_HOME
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$JAVACMD" ] ; then
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
else
|
||||
JAVACMD="`which java`"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
echo "Error: JAVA_HOME is not defined correctly." >&2
|
||||
echo " We cannot execute $JAVACMD" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ] ; then
|
||||
echo "Warning: JAVA_HOME environment variable is not set."
|
||||
fi
|
||||
|
||||
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin; then
|
||||
[ -n "$M2_HOME" ] &&
|
||||
M2_HOME=`cygpath --path --windows "$M2_HOME"`
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
|
||||
[ -n "$CLASSPATH" ] &&
|
||||
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
|
||||
fi
|
||||
|
||||
# traverses directory structure from process work directory to filesystem root
|
||||
# first directory with .mvn subdirectory is considered project base directory
|
||||
find_maven_basedir() {
|
||||
local basedir=$(pwd)
|
||||
local wdir=$(pwd)
|
||||
while [ "$wdir" != '/' ] ; do
|
||||
wdir=$(cd "$wdir/.."; pwd)
|
||||
if [ -d "$wdir"/.mvn ] ; then
|
||||
basedir=$wdir
|
||||
break
|
||||
fi
|
||||
done
|
||||
echo "${basedir}"
|
||||
}
|
||||
|
||||
# concatenates all lines of a file
|
||||
concat_lines() {
|
||||
if [ -f "$1" ]; then
|
||||
echo "$(tr -s '\n' ' ' < "$1")"
|
||||
fi
|
||||
}
|
||||
|
||||
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)}
|
||||
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
|
||||
|
||||
# Provide a "standardized" way to retrieve the CLI args that will
|
||||
# work with both Windows and non-Windows executions.
|
||||
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
|
||||
export MAVEN_CMD_LINE_ARGS
|
||||
|
||||
WRAPPER_LAUNCHER="org.apache.maven.wrapper.MavenWrapperMain"
|
||||
|
||||
exec "$JAVACMD" \
|
||||
$MAVEN_OPTS \
|
||||
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
|
||||
-classpath \
|
||||
"$MAVEN_PROJECTBASEDIR/maven/maven-wrapper.jar" \
|
||||
${WRAPPER_LAUNCHER} "$@"
|
||||
141
mvnw.bat
Normal file
141
mvnw.bat
Normal file
@@ -0,0 +1,141 @@
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Licensed to the Apache Software Foundation (ASF) under one
|
||||
@REM or more contributor license agreements. See the NOTICE file
|
||||
@REM distributed with this work for additional information
|
||||
@REM regarding copyright ownership. The ASF licenses this file
|
||||
@REM to you under the Apache License, Version 2.0 (the
|
||||
@REM "License"); you may not use this file except in compliance
|
||||
@REM with the License. You may obtain a copy of the License at
|
||||
@REM
|
||||
@REM http://www.apache.org/licenses/LICENSE-2.0
|
||||
@REM
|
||||
@REM Unless required by applicable law or agreed to in writing,
|
||||
@REM software distributed under the License is distributed on an
|
||||
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
@REM KIND, either express or implied. See the License for the
|
||||
@REM specific language governing permissions and limitations
|
||||
@REM under the License.
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Maven2 Start Up Batch script
|
||||
@REM
|
||||
@REM Required ENV vars:
|
||||
@REM JAVA_HOME - location of a JDK home dir
|
||||
@REM
|
||||
@REM Optional ENV vars
|
||||
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
|
||||
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
|
||||
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||
@REM e.g. to debug Maven itself, use
|
||||
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
|
||||
@echo off
|
||||
@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
|
||||
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
|
||||
|
||||
@REM set %HOME% to equivalent of $HOME
|
||||
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
|
||||
|
||||
@REM Execute a user defined script before this one
|
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
|
||||
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
|
||||
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
|
||||
:skipRcPre
|
||||
|
||||
@setlocal
|
||||
|
||||
set ERROR_CODE=0
|
||||
|
||||
@REM To isolate internal variables from possible post scripts, we use another setlocal
|
||||
@setlocal
|
||||
|
||||
@REM ==== START VALIDATION ====
|
||||
if not "%JAVA_HOME%" == "" goto OkJHome
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME not found in your environment. >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
:OkJHome
|
||||
if exist "%JAVA_HOME%\bin\java.exe" goto init
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME is set to an invalid directory. >&2
|
||||
echo JAVA_HOME = "%JAVA_HOME%" >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
:init
|
||||
|
||||
set MAVEN_CMD_LINE_ARGS=%*
|
||||
|
||||
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
|
||||
@REM Fallback to current working directory if not found.
|
||||
|
||||
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
|
||||
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
|
||||
|
||||
set EXEC_DIR=%CD%
|
||||
set WDIR=%EXEC_DIR%
|
||||
:findBaseDir
|
||||
IF EXIST "%WDIR%"\.mvn goto baseDirFound
|
||||
cd ..
|
||||
IF "%WDIR%"=="%CD%" goto baseDirNotFound
|
||||
set WDIR=%CD%
|
||||
goto findBaseDir
|
||||
|
||||
:baseDirFound
|
||||
set MAVEN_PROJECTBASEDIR=%WDIR%
|
||||
cd "%EXEC_DIR%"
|
||||
goto endDetectBaseDir
|
||||
|
||||
:baseDirNotFound
|
||||
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
|
||||
cd "%EXEC_DIR%"
|
||||
|
||||
:endDetectBaseDir
|
||||
|
||||
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
|
||||
|
||||
@setlocal EnableExtensions EnableDelayedExpansion
|
||||
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
|
||||
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
|
||||
|
||||
:endReadAdditionalConfig
|
||||
|
||||
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
|
||||
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\maven\maven-wrapper.jar"
|
||||
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
%MAVEN_JAVA_EXE% -Dmaven.multiModuleProjectDirectory="" %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS%
|
||||
|
||||
if ERRORLEVEL 1 goto error
|
||||
goto end
|
||||
|
||||
:error
|
||||
set ERROR_CODE=1
|
||||
|
||||
:end
|
||||
@endlocal & set ERROR_CODE=%ERROR_CODE%
|
||||
|
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
|
||||
@REM check for post script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
|
||||
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
|
||||
:skipRcPost
|
||||
|
||||
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
|
||||
if "%MAVEN_BATCH_PAUSE%" == "on" pause
|
||||
|
||||
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
|
||||
|
||||
exit /B %ERROR_CODE%
|
||||
24
package.json
24
package.json
@@ -4,17 +4,17 @@
|
||||
"main": "main.js",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"gulp": "3.8.7",
|
||||
"gulp-rev": "1.0.0",
|
||||
"gulp-rev-replace": "0.3.0",
|
||||
"gulp-minify-css": "0.3.7",
|
||||
"gulp-uglify": "0.3.1",
|
||||
"gulp-filter": "1.0.0",
|
||||
"gulp-bower": "0.0.6",
|
||||
"gulp-connect": "2.0.6",
|
||||
"connect-modrewrite": "0.7.7",
|
||||
"gulp-sass": "0.7.2",
|
||||
"gulp-useref": "0.6.0",
|
||||
"gulp-angular-templatecache": "1.3.0"
|
||||
"bower": "1.4.1",
|
||||
"gulp": "3.8.11",
|
||||
"gulp-rev": "4.0.0",
|
||||
"gulp-rev-replace": "0.4.1",
|
||||
"gulp-minify-css": "1.1.5",
|
||||
"gulp-uglify": "1.2.0",
|
||||
"gulp-filter": "2.0.2",
|
||||
"gulp-connect": "2.2.0",
|
||||
"connect-modrewrite": "0.8.1",
|
||||
"gulp-sass": "2.0.2",
|
||||
"gulp-useref": "1.1.2",
|
||||
"gulp-angular-templatecache": "1.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
269
pom.xml
269
pom.xml
@@ -4,19 +4,16 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.commafeed</groupId>
|
||||
<artifactId>commafeed</artifactId>
|
||||
<version>2.0.3</version>
|
||||
<version>2.4.0</version>
|
||||
<packaging>jar</packaging>
|
||||
<name>CommaFeed</name>
|
||||
|
||||
<prerequisites>
|
||||
<maven>3.0.0</maven>
|
||||
</prerequisites>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<dropwizard.version>0.7.1</dropwizard.version>
|
||||
<guice.version>3.0</guice.version>
|
||||
<querydsl.version>3.5.0</querydsl.version>
|
||||
<java.version>1.8</java.version>
|
||||
<dropwizard.version>0.9.1</dropwizard.version>
|
||||
<guice.version>4.0</guice.version>
|
||||
<querydsl.version>4.0.2</querydsl.version>
|
||||
<rome.version>1.5.0</rome.version>
|
||||
</properties>
|
||||
|
||||
@@ -34,20 +31,53 @@
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.eclipse.m2e</groupId>
|
||||
<artifactId>lifecycle-mapping</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<configuration>
|
||||
<lifecycleMappingMetadata>
|
||||
<pluginExecutions>
|
||||
<pluginExecution>
|
||||
<pluginExecutionFilter>
|
||||
<groupId>com.github.eirslett</groupId>
|
||||
<artifactId>frontend-maven-plugin</artifactId>
|
||||
<versionRange>[0.0.22,)</versionRange>
|
||||
<goals>
|
||||
<goal>npm</goal>
|
||||
<goal>gulp</goal>
|
||||
<goal>bower</goal>
|
||||
</goals>
|
||||
</pluginExecutionFilter>
|
||||
<action>
|
||||
<execute>
|
||||
<runOnIncremental>false</runOnIncremental>
|
||||
</execute>
|
||||
</action>
|
||||
</pluginExecution>
|
||||
</pluginExecutions>
|
||||
</lifecycleMappingMetadata>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.2</version>
|
||||
<version>3.1</version>
|
||||
<configuration>
|
||||
<source>1.7</source>
|
||||
<target>1.7</target>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>pl.project13.maven</groupId>
|
||||
<artifactId>git-commit-id-plugin</artifactId>
|
||||
<version>2.1.11</version>
|
||||
<version>2.1.13</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
@@ -95,20 +125,49 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.github.kongchen</groupId>
|
||||
<artifactId>swagger-maven-plugin</artifactId>
|
||||
<version>3.1.1</version>
|
||||
<configuration>
|
||||
<apiSources>
|
||||
<apiSource>
|
||||
<locations>com.commafeed.frontend.resource;com.commafeed.frontend.model;com.commafeed.frontend.model.request</locations>
|
||||
<swaggerDirectory>target/swagger</swaggerDirectory>
|
||||
<basePath>/rest</basePath>
|
||||
<info>
|
||||
<title>CommaFeed</title>
|
||||
<version>${project.version}</version>
|
||||
</info>
|
||||
<typesToSkip>
|
||||
<typeToSkip>com.commafeed.backend.model.User</typeToSkip>
|
||||
</typesToSkip>
|
||||
</apiSource>
|
||||
</apiSources>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>compile</phase>
|
||||
<goals>
|
||||
<goal>generate</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.github.eirslett</groupId>
|
||||
<artifactId>frontend-maven-plugin</artifactId>
|
||||
<version>0.0.16</version>
|
||||
<version>0.0.25</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>install node and npm</id>
|
||||
<goals>
|
||||
<goal>install-node-and-npm</goal>
|
||||
</goals>
|
||||
<phase>generate-resources</phase>
|
||||
<phase>compile</phase>
|
||||
<configuration>
|
||||
<nodeVersion>v0.10.30</nodeVersion>
|
||||
<npmVersion>1.3.8</npmVersion>
|
||||
<nodeVersion>v0.10.39</nodeVersion>
|
||||
<npmVersion>3.10.6</npmVersion>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
@@ -116,21 +175,34 @@
|
||||
<goals>
|
||||
<goal>npm</goal>
|
||||
</goals>
|
||||
<phase>generate-resources</phase>
|
||||
<phase>compile</phase>
|
||||
<configuration>
|
||||
<arguments>install</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>bower install</id>
|
||||
<goals>
|
||||
<goal>bower</goal>
|
||||
</goals>
|
||||
<phase>compile</phase>
|
||||
<configuration>
|
||||
<arguments>install</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>gulp build</id>
|
||||
<goals>
|
||||
<goal>gulp</goal>
|
||||
</goals>
|
||||
<phase>generate-resources</phase>
|
||||
<phase>compile</phase>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.5</version>
|
||||
<version>2.6</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
@@ -139,29 +211,6 @@
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.jamierf.dropwizard</groupId>
|
||||
<artifactId>dropwizard-debpkg-maven-plugin</artifactId>
|
||||
<version>0.7</version>
|
||||
<configuration>
|
||||
<configTemplate>${basedir}/config.yml.example</configTemplate>
|
||||
<jvm>
|
||||
<packageName>openjdk-7-jdk</packageName>
|
||||
<server>true</server>
|
||||
</jvm>
|
||||
<unix>
|
||||
<user>commafeed</user>
|
||||
</unix>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>dwpackage</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
@@ -169,13 +218,13 @@
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.14.8</version>
|
||||
<version>1.16.4</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>1.7.7</version>
|
||||
<version>1.7.12</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -193,17 +242,22 @@
|
||||
<groupId>io.dropwizard</groupId>
|
||||
<artifactId>dropwizard-core</artifactId>
|
||||
<version>${dropwizard.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.glassfish.hk2.external</groupId>
|
||||
<artifactId>aopalliance-repackaged</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.glassfish.hk2.external</groupId>
|
||||
<artifactId>javax.inject</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard</groupId>
|
||||
<artifactId>dropwizard-hibernate</artifactId>
|
||||
<version>${dropwizard.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard</groupId>
|
||||
<artifactId>dropwizard-client</artifactId>
|
||||
<version>${dropwizard.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard</groupId>
|
||||
<artifactId>dropwizard-migrations</artifactId>
|
||||
@@ -218,34 +272,55 @@
|
||||
<groupId>io.dropwizard</groupId>
|
||||
<artifactId>dropwizard-forms</artifactId>
|
||||
<version>${dropwizard.version}</version>
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.wordnik</groupId>
|
||||
<artifactId>swagger-jaxrs_2.10</artifactId>
|
||||
<version>1.3.10</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>javax.ws.rs</groupId>
|
||||
<artifactId>jsr311-api</artifactId>
|
||||
<groupId>org.glassfish.hk2.external</groupId>
|
||||
<artifactId>javax.inject</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.dropwizard.metrics</groupId>
|
||||
<artifactId>metrics-graphite</artifactId>
|
||||
<version>3.1.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.mysema.querydsl</groupId>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>4.5</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
<groupId>commons-logging</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
<version>1.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.querydsl</groupId>
|
||||
<artifactId>querydsl-apt</artifactId>
|
||||
<version>${querydsl.version}</version>
|
||||
<scope>provided</scope>
|
||||
<classifier>hibernate</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.mysema.querydsl</groupId>
|
||||
<groupId>com.querydsl</groupId>
|
||||
<artifactId>querydsl-jpa</artifactId>
|
||||
<version>${querydsl.version}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>18.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
@@ -254,70 +329,106 @@
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-collections4</artifactId>
|
||||
<version>4.0</version>
|
||||
<version>4.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
<version>1.9</version>
|
||||
<version>1.10</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-math3</artifactId>
|
||||
<version>3.3</version>
|
||||
<version>3.5</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-jexl</artifactId>
|
||||
<version>2.1.1</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
<groupId>commons-logging</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>redis.clients</groupId>
|
||||
<artifactId>jedis</artifactId>
|
||||
<version>2.6.0</version>
|
||||
<version>2.7.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.sun.mail</groupId>
|
||||
<artifactId>javax.mail</artifactId>
|
||||
<version>1.5.2</version>
|
||||
<version>1.5.3</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- upgrade jdom to 2.0.5 for performance reasons (https://github.com/hunterhacker/jdom/issues/112) -->
|
||||
<dependency>
|
||||
<groupId>com.rometools</groupId>
|
||||
<artifactId>rome</artifactId>
|
||||
<version>${rome.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>jdom</artifactId>
|
||||
<groupId>org.jdom</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.rometools</groupId>
|
||||
<artifactId>rome-opml</artifactId>
|
||||
<version>${rome.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jdom</groupId>
|
||||
<artifactId>jdom2</artifactId>
|
||||
<version>2.0.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<version>1.8.1</version>
|
||||
<version>1.8.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.googlecode.juniversalchardet</groupId>
|
||||
<artifactId>juniversalchardet</artifactId>
|
||||
<version>1.0.3</version>
|
||||
<groupId>com.ibm.icu</groupId>
|
||||
<artifactId>icu4j</artifactId>
|
||||
<version>55.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sourceforge.cssparser</groupId>
|
||||
<artifactId>cssparser</artifactId>
|
||||
<version>0.9.14</version>
|
||||
<version>0.9.16</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.apis</groupId>
|
||||
<artifactId>google-api-services-youtube</artifactId>
|
||||
<version>v3-rev139-1.20.0</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava-jdk5</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<version>1.4.182</version>
|
||||
<version>1.3.176</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>5.1.33</version>
|
||||
<version>5.1.37</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>9.3-1102-jdbc41</version>
|
||||
<version>9.4.1212</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sourceforge.jtds</groupId>
|
||||
@@ -328,13 +439,13 @@
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.11</version>
|
||||
<version>4.12</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>1.10.8</version>
|
||||
<version>2.0.11-beta</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<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/typography.css' media='screen' rel='stylesheet' type='text/css'/>
|
||||
<link href='css/reset.css' media='screen' rel='stylesheet' type='text/css'/>
|
||||
<link href='css/screen.css' media='screen' rel='stylesheet' type='text/css'/>
|
||||
<link href='css/reset.css' media='print' rel='stylesheet' type='text/css'/>
|
||||
@@ -12,25 +12,23 @@
|
||||
<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.0.js' type='text/javascript'></script>
|
||||
<script src='lib/handlebars-2.0.0.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='lib/swagger-client.js' type='text/javascript'></script>
|
||||
<script src='swagger-ui.js' type='text/javascript'></script>
|
||||
<script src='lib/highlight.7.3.pack.js' type='text/javascript'></script>
|
||||
<script src='lib/marked.js' type='text/javascript'></script>
|
||||
|
||||
<!-- enabling this will enable oauth2 implicit scope support -->
|
||||
<script src='lib/swagger-oauth.js' type='text/javascript'></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function () {
|
||||
window.swaggerUi = new SwaggerUi({
|
||||
url: "../rest/api-docs",
|
||||
url: "./swagger.json",
|
||||
dom_id: "swagger-ui-container",
|
||||
supportedSubmitMethods: ['get', 'post', 'put', 'delete'],
|
||||
onComplete: function(swaggerApi, swaggerUi){
|
||||
log("Loaded SwaggerUI");
|
||||
|
||||
if(typeof initOAuth == "function") {
|
||||
/*
|
||||
initOAuth({
|
||||
|
||||
BIN
src/main/app/app-icon-192.png
Normal file
BIN
src/main/app/app-icon-192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.9 KiB |
@@ -41,6 +41,7 @@
|
||||
"refresh" : "إعادة انعاش",
|
||||
"refresh_all" : "Force refresh all my feeds ",
|
||||
"sort_by_asc_desc" : "الترتيب حسب التاريخ تصاعدي / تنازلي",
|
||||
"sort_by_abc_zyx" : "Sort alphabetically",
|
||||
"titles_only" : "العناوين فقط",
|
||||
"expanded_view" : "عرض موسع",
|
||||
"mark_all_as_read" : "اعتبر الكل مقروء",
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"refresh" : "Actualitzar",
|
||||
"refresh_all" : "Força l'actualització de tots els canals",
|
||||
"sort_by_asc_desc" : "Ordenar per data asc/desc",
|
||||
"sort_by_abc_zyx" : "Sort alphabetically",
|
||||
"titles_only" : "Només títols",
|
||||
"expanded_view" : "Vista ampliada",
|
||||
"mark_all_as_read" : "Marcar tots llegits",
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"refresh " : " Obnovit",
|
||||
"refresh_all" : "Force refresh all my feeds ",
|
||||
"sort_by_asc_desc " : " Seřadit podle nejnovějšího/nejstaršího",
|
||||
"sort_by_abc_zyx" : "Sort alphabetically",
|
||||
"titles_only " : " Zobrazit jenom titulky",
|
||||
"expanded_view " : " Rozšířený náhled",
|
||||
"mark_all_as_read " : " Označit vše jako přečtené",
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"refresh" : "Adnewyddu",
|
||||
"refresh_all" : "Gorfodi ail-lwytho pob ffrwd",
|
||||
"sort_by_asc_desc" : "Trefnu yn ôl dyddiad",
|
||||
"sort_by_abc_zyx" : "Sort alphabetically",
|
||||
"titles_only" : "Teitlau yn unig",
|
||||
"expanded_view" : "Golwg estynedig",
|
||||
"mark_all_as_read" : "Nodi'r cyfan fel wedi ei ddarllen",
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"refresh" : "Opdater",
|
||||
"refresh_all" : "Force refresh all my feeds ",
|
||||
"sort_by_asc_desc" : "Sorter efter dato ny/gammel",
|
||||
"sort_by_abc_zyx" : "Sort alphabetically",
|
||||
"titles_only" : "Kun titler",
|
||||
"expanded_view" : "Udvidet visning",
|
||||
"mark_all_as_read" : "Marker alle som læst",
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"refresh" : "Aktualisieren",
|
||||
"refresh_all" : "Erzwinge Aktualisierung aller Feeds",
|
||||
"sort_by_asc_desc" : "Nach Datum sortieren (auf-/absteigend)",
|
||||
"sort_by_abc_zyx" : "Sort Alphabetically",
|
||||
"titles_only" : "Nur Überschriften",
|
||||
"expanded_view" : "Ausgedehnte Ansicht",
|
||||
"mark_all_as_read" : "Alle Artikel als gelesen markieren",
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"refresh" : "Refresh",
|
||||
"refresh_all" : "Force refresh all my feeds",
|
||||
"sort_by_asc_desc" : "Sort by date asc/desc",
|
||||
"sort_by_abc_zyx" : "Sort alphabetically",
|
||||
"titles_only" : "Titles only",
|
||||
"expanded_view" : "Expanded view",
|
||||
"mark_all_as_read" : "Mark all as read",
|
||||
@@ -98,6 +99,8 @@
|
||||
"next_refresh" : "Next refresh",
|
||||
"queued_for_refresh" : "Queued for refresh",
|
||||
"feed_url" : "Feed URL",
|
||||
"filtering_expression" : "Filtering expression",
|
||||
"filtering_expression_help" : "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically.\nAvailable variables are 'title', 'content', 'url' 'author' and 'categories' and their content is converted to lower case to ease string comparison.\nExample: url.contains('youtube') or (author eq 'athou' and title.contains('github').\nComplete available syntax is available <a href='http://commons.apache.org/proper/commons-jexl/reference/syntax.html' target='_blank'>here</a>.",
|
||||
"generate_api_key_first" : "Generate an API key in your profile first.",
|
||||
"unsubscribe" : "Unsubscribe",
|
||||
"unsubscribe_confirmation" : "Are you sure you want to unsubscribe from this feed?",
|
||||
|
||||
@@ -7,24 +7,26 @@
|
||||
"download" : "Descargar",
|
||||
"link" : "Enlace",
|
||||
"bookmark" : "Marcador",
|
||||
"close" : "Close ",
|
||||
"tags" : "Tags "
|
||||
"close" : "Cerrar",
|
||||
"tags" : "Etiquetas"
|
||||
},
|
||||
"tree" : {
|
||||
"subscribe" : "Subscribir",
|
||||
"subscribe" : "Suscribirse",
|
||||
"import" : "Importar",
|
||||
"new_category" : "Nueva categoría",
|
||||
"all" : "Todos",
|
||||
"starred" : "Destacado"
|
||||
"starred" : "Destacados"
|
||||
},
|
||||
"subscribe" : {
|
||||
"feed_url" : "URL del Canal",
|
||||
"feed_name" : "Nombre del Canal",
|
||||
"feed_url" : "URL del canal",
|
||||
"filtering_expression" : "Expresión de filtrado",
|
||||
"filtering_expression_help" : "Si no está vacía, una expresión se evalúa como 'cierta' o 'falsa'. Si es falsa, las nueva entradas de este canal se marcarán como leídas automáticamente.\nLas variables disponibles son 'title' (título), 'content'(contenido), 'url' (URL), 'author' (autor), y 'categories' (categorías) y sus contenidos son convertidos a minúsculas para facilitar la comparación de strings (cadenas de texto).\nEjemplo: url.contains('youtube') or (author eq 'athou' and title.contains('github').\nLa sintaxis completa está disponible <a href='http://commons.apache.org/proper/commons-jexl/reference/syntax.html' target='_blank'>aquí</a>.",
|
||||
"feed_name" : "Nombre del canal",
|
||||
"category" : "Categoría"
|
||||
},
|
||||
"import" : {
|
||||
"google_reader_prefix" : "Déjame importar tus canales de tu",
|
||||
"google_reader_suffix" : " cuenta.",
|
||||
"google_reader_prefix" : "Déjame importar tus canales de tu cuenta ",
|
||||
"google_reader_suffix" : ".",
|
||||
"google_download" : "También puedes subir tu archivo subscriptions.xml.",
|
||||
"google_download_link" : "Descárgalo de aquí.",
|
||||
"xml_file" : "Archivo OPML"
|
||||
@@ -34,147 +36,148 @@
|
||||
"parent" : "Padre"
|
||||
},
|
||||
"toolbar" : {
|
||||
"unread" : "Sin Leer",
|
||||
"unread" : "No leídos",
|
||||
"all" : "Todos",
|
||||
"previous_entry" : "Entrada Anterior",
|
||||
"next_entry" : "Próxima Entrada",
|
||||
"refresh" : "Atualizar",
|
||||
"refresh_all" : "Force refresh all my feeds ",
|
||||
"sort_by_asc_desc" : "Ordenar por fecha asc/desc",
|
||||
"titles_only" : "Sólo Títulos",
|
||||
"expanded_view" : "Vista Expandida",
|
||||
"previous_entry" : "Entrada anterior",
|
||||
"next_entry" : "Entrada siguiente",
|
||||
"refresh" : "Actualizar",
|
||||
"refresh_all" : "Forzar la actualización de todos mis canales.",
|
||||
"sort_by_asc_desc" : "Ordenar por fecha asc/desc.",
|
||||
"sort_by_abc_zyx" : "Ordenar alfabéticamente",
|
||||
"titles_only" : "Sólo títulos",
|
||||
"expanded_view" : "Vista expandida",
|
||||
"mark_all_as_read" : "Marcar todos como leído",
|
||||
"mark_all_older_12_hours" : "Items older than 12 hours ",
|
||||
"mark_all_older_day" : "Artículos anteriores a un día",
|
||||
"mark_all_older_week" : "Artículos más de una semana",
|
||||
"mark_all_older_two_weeks" : "Artículos más de does semanas",
|
||||
"mark_all_older_12_hours" : "Entradas anteriores a 12 horas.",
|
||||
"mark_all_older_day" : "Entradas anteriores a un día.",
|
||||
"mark_all_older_week" : "Entradas anteriores a una semana.",
|
||||
"mark_all_older_two_weeks" : "Entradas anteriores a 2 semanas.",
|
||||
"settings" : "Ajustes",
|
||||
"profile" : "Perfil",
|
||||
"admin" : "Admin",
|
||||
"about" : "Acerca de",
|
||||
"about" : "Acerca de...",
|
||||
"logout" : "Cerrar sesión",
|
||||
"donate" : "Donar"
|
||||
},
|
||||
"view" : {
|
||||
"entry_source" : "from ",
|
||||
"entry_author" : "by ",
|
||||
"error_while_loading_feed" : "Error mientras se cargaba este canal",
|
||||
"keep_unread" : "Guardar no leídos",
|
||||
"no_unread_items" : "no tiene items sin leer.",
|
||||
"mark_up_to_here" : "Mark as read up to here ",
|
||||
"search_for" : "searching for: ",
|
||||
"no_search_results" : "No match found for the requested keywords "
|
||||
"entry_source" : "de ",
|
||||
"entry_author" : "por ",
|
||||
"error_while_loading_feed" : "Error mientras se cargaba este canal.",
|
||||
"keep_unread" : "Mantener como no leído.",
|
||||
"no_unread_items" : "no tiene entradas sin leer.",
|
||||
"mark_up_to_here" : "Marcar como leídos hasta aquí.",
|
||||
"search_for" : "buscando: ",
|
||||
"no_search_results" : "No se han encontrado resultados para las palabras clave especificadas."
|
||||
},
|
||||
"feedsearch" : {
|
||||
"hint" : "Type in a subscription... ",
|
||||
"help" : "Use the return key to select and arrow keys to navigate. ",
|
||||
"result_prefix" : "Your subscriptions: "
|
||||
"hint" : "Introduce una suscripción...",
|
||||
"help" : "Usa la tecla Intro para seleccionar y las teclas de flecha para navegar.",
|
||||
"result_prefix" : "Tus suscripciones:"
|
||||
},
|
||||
"settings" : {
|
||||
"general" : {
|
||||
"value" : "General",
|
||||
"language" : "Lenguaje",
|
||||
"language_contribute" : "Contribuye con traducciones",
|
||||
"language" : "Idioma",
|
||||
"language_contribute" : "Contribuye con traducciones.",
|
||||
"show_unread" : "Mostrar canales y categorías sin entradas no leídas.",
|
||||
"social_buttons" : "Mostrar botones de compartir de redes sociales.",
|
||||
"scroll_marks" : "En vista expandida, el desplazamiento por las entradas las marca como leídas"
|
||||
"social_buttons" : "Mostrar botones para compartir de redes sociales.",
|
||||
"scroll_marks" : "En vista expandida, el desplazamiento por las entradas las marca como leídas."
|
||||
},
|
||||
"appearance" : "Appearance ",
|
||||
"scroll_speed" : "Scrolling speed when navigating between entries (in milliseconds) ",
|
||||
"scroll_speed_help" : "set to 0 to disable ",
|
||||
"theme" : "Theme ",
|
||||
"submit_your_theme" : "Submit your theme ",
|
||||
"custom_css" : "CSS Personalizado"
|
||||
"appearance" : "Apariencia",
|
||||
"scroll_speed" : "Velocidad de desplazamiento al navegar entre entradas (en milisegundos)",
|
||||
"scroll_speed_help" : "ponlo a 0 para desactivarlo",
|
||||
"theme" : "Tema",
|
||||
"submit_your_theme" : "Envía tu tema ",
|
||||
"custom_css" : "CSS personalizado"
|
||||
},
|
||||
"details" : {
|
||||
"feed_details" : "Detalles de Canales",
|
||||
"feed_details" : "Detalles del canal",
|
||||
"url" : "URL",
|
||||
"website" : "Website ",
|
||||
"website" : "Sitio web",
|
||||
"name" : "Nombre",
|
||||
"category" : "Categoría",
|
||||
"position" : "Position ",
|
||||
"position" : "Posición",
|
||||
"last_refresh" : "Última actualización",
|
||||
"message" : "Last refresh message ",
|
||||
"next_refresh" : "Next refresh ",
|
||||
"queued_for_refresh" : "Queued for refresh ",
|
||||
"feed_url" : "URL del Canal",
|
||||
"generate_api_key_first" : "Genera una llave API en tu perfil primero.",
|
||||
"unsubscribe" : "Terminar subscripción",
|
||||
"unsubscribe_confirmation" : "Are you sure you want to unsubscribe from this feed? ",
|
||||
"delete_category_confirmation" : "Are you sure you want to delete this category? ",
|
||||
"message" : "Último mensaje de actualización",
|
||||
"next_refresh" : "Próxima actualización",
|
||||
"queued_for_refresh" : "En cola para actualizar",
|
||||
"feed_url" : "URL del canal",
|
||||
"generate_api_key_first" : "Genera una clave API en tu perfil primero.",
|
||||
"unsubscribe" : "Terminar suscripción",
|
||||
"unsubscribe_confirmation" : "¿Estás seguro de querer terminar tu suscripción a este canal?",
|
||||
"delete_category_confirmation" : "¿Estás seguro de querer eliminar esta categoría?",
|
||||
"category_details" : "Detalles de la categoría",
|
||||
"tag_details" : "Tag details ",
|
||||
"tag_details" : "Detalles de las etiquetas ",
|
||||
"parent_category" : "Categoría principal"
|
||||
},
|
||||
"profile" : {
|
||||
"user_name" : "Nombre de usuario",
|
||||
"email" : "Correo",
|
||||
"email" : "Correo electrónico",
|
||||
"change_password" : "Cambiar contraseña",
|
||||
"confirm_password" : "Confirmar contraseña",
|
||||
"minimum_6_chars" : "Mínimo 6 caracteres",
|
||||
"passwords_do_not_match" : "Las contraseñas no coinciden",
|
||||
"api_key" : "Llave API",
|
||||
"api_key" : "Clave API",
|
||||
"api_key_not_generated" : "No generado todavía",
|
||||
"generate_new_api_key" : "Generar nueva llave API",
|
||||
"generate_new_api_key_info" : "Al cambiar la contraseña se generará una nueva llave API",
|
||||
"generate_new_api_key" : "Generar nueva clave API",
|
||||
"generate_new_api_key_info" : "Al cambiar la contraseña se generará una nueva clave API.",
|
||||
"opml_export" : "Exportación de OPML",
|
||||
"delete_account" : "Eliminar cuenta",
|
||||
"delete_account_confirmation" : "Delete your acount? There's no turning back! "
|
||||
"delete_account_confirmation" : "¿Eliminar tu cuenta? ¡No habrá vuelta atrás! "
|
||||
},
|
||||
"about" : {
|
||||
"rest_api" : {
|
||||
"value" : "REST API",
|
||||
"line1" : "CommaFeed está construido con el uso de JAX-RS y AngularJS. Por eso, un REST API esta disponible.",
|
||||
"link_to_documentation" : "Vínculo de la documentación."
|
||||
"line1" : "CommaFeed está construido sobre JAX-RS y AngularJS. Por lo tanto, una REST API está disponible.",
|
||||
"link_to_documentation" : "Enlace a la documentación."
|
||||
},
|
||||
"keyboard_shortcuts" : "Atajos de teclado",
|
||||
"version" : "CommaFeed version ",
|
||||
"line1_prefix" : "CommaFeed es un proyecto open-source. El código se encuentra en ",
|
||||
"version" : "Versión de CommaFeed",
|
||||
"line1_prefix" : "CommaFeed es un proyecto de código abierto. El código se encuentra en ",
|
||||
"line1_suffix" : ".",
|
||||
"line2_prefix" : "Si encuentras un problema, por favor reportalo en la página de problemas de ",
|
||||
"line2_prefix" : "Si encuentras un problema, por favor repórtalo en la página de problemas de ",
|
||||
"line2_suffix" : " del proyecto.",
|
||||
"line3" : "Si te gusta este proyecto, por favor considera realizar una donacion para apoyar al desarrollador y ayudar a cubrir los costos de mantenimiento.",
|
||||
"line4" : "For those of you who prefer bitcoin, here is the address ",
|
||||
"line3" : "Si te gusta este proyecto, por favor considera realizar una donación para apoyar al desarrollador y ayudar a cubrir los costes de mantenimiento.",
|
||||
"line4" : "Para aquellos de vosotros que prefieran bitcoin, aquí está la dirección ",
|
||||
"goodies" : {
|
||||
"value" : "Goodies",
|
||||
"android_app" : "Android app ",
|
||||
"subscribe_url" : "Subscribe URL ",
|
||||
"chrome_extension" : "Extensión de Chrome",
|
||||
"firefox_extension" : "Extensión de Firefox",
|
||||
"opera_extension" : "Opera extension ",
|
||||
"subscribe_bookmarklet" : "Add subscription bookmarklet (click) ",
|
||||
"subscribe_bookmarklet_asc" : "Oldest first ",
|
||||
"subscribe_bookmarklet_desc" : "Newest first ",
|
||||
"next_unread_bookmarklet" : "Next unread item bookmarklet (drag to bookmark bar) "
|
||||
"value" : "Extras",
|
||||
"android_app" : "Apps para Android",
|
||||
"subscribe_url" : "URL para suscribirse ",
|
||||
"chrome_extension" : "Extensión para Chrome.",
|
||||
"firefox_extension" : "Extensión para Firefox.",
|
||||
"opera_extension" : "Extensón para Opera.",
|
||||
"subscribe_bookmarklet" : "Añadir marcador de suscripción (clic).",
|
||||
"subscribe_bookmarklet_asc" : "Más antiguos primero",
|
||||
"subscribe_bookmarklet_desc" : "Más recientes primero",
|
||||
"next_unread_bookmarklet" : "Marcador a la siguiente entrada no leída (arástralo a la barra de marcadores) "
|
||||
},
|
||||
"translation" : {
|
||||
"value" : "Traducción",
|
||||
"message" : "Necesitamos tu ayuda para ayudar a traducir CommaFeed.",
|
||||
"link" : "Ver como contribuir con traducciones."
|
||||
"link" : "Ver cómo contribuir con traducciones."
|
||||
},
|
||||
"announcements" : "Anuncios",
|
||||
"shortcuts" : {
|
||||
"mouse_middleclick" : "ratón botón medio",
|
||||
"open_next_entry" : "abrir próxima entrada",
|
||||
"open_previous_entry" : "abrir entrada previa",
|
||||
"spacebar" : "space/shift+space ",
|
||||
"move_page_down_up" : "moves the page down/up ",
|
||||
"focus_next_entry" : "Establecer el foco en la próxima entrada sin abrirlo",
|
||||
"focus_previous_entry" : "Establecer el foco en la entrada anterior sin abrirlo",
|
||||
"open_next_feed" : "open next feed or category ",
|
||||
"open_previous_feed" : "open previous feed or category ",
|
||||
"open_close_current_entry" : "abrir/cerrar entrada actual",
|
||||
"open_current_entry_in_new_window" : "abrir entrada actual en una nueva ventana",
|
||||
"open_current_entry_in_new_window_background" : "open current entry in a new window in the background ",
|
||||
"star_unstar" : "marcar/no marcar la entrada actual",
|
||||
"mark_current_entry" : "marcar como leída/no la leída entrada actual",
|
||||
"mouse_middleclick" : "click medio",
|
||||
"open_next_entry" : "abrir la siguiente entrada",
|
||||
"open_previous_entry" : "abrir la entrada anterior",
|
||||
"spacebar" : "espacio/mayúsculas+espacio",
|
||||
"move_page_down_up" : "mueve la página arriba/abajo",
|
||||
"focus_next_entry" : "establecer el foco en la siguiente entrada sin abrirla",
|
||||
"focus_previous_entry" : "establecer el foco en la entrada anterior sin abrirla",
|
||||
"open_next_feed" : "abrir el siguiente canal o categoría",
|
||||
"open_previous_feed" : "abrir el canal o categoría previo",
|
||||
"open_close_current_entry" : "abrir/cerrar la entrada actual",
|
||||
"open_current_entry_in_new_window" : "abrir la entrada actual en una nueva ventana",
|
||||
"open_current_entry_in_new_window_background" : "abrir la entrada actual en una nueva ventana en segundo plano",
|
||||
"star_unstar" : "destacar la entrada actual",
|
||||
"mark_current_entry" : "marcar la entrada actual como leída/no la leída",
|
||||
"mark_all_as_read" : "marcar todas las entradas como leídas",
|
||||
"open_in_new_tab_mark_as_read" : "abrir entrada en una nueva pestaña y marcar como leída",
|
||||
"fullscreen" : "toggle full screen mode ",
|
||||
"font_size" : "increase/decrease font size of the current entry ",
|
||||
"go_to_all" : "go to the All view ",
|
||||
"go_to_starred" : "go to the Starred view ",
|
||||
"feed_search" : "navigate to a subscription by entering the subscription name "
|
||||
"fullscreen" : "activar/desactivar el modo pantalla completa ",
|
||||
"font_size" : "aumentar/reducir el tamaño de la fuente de la entrada actual",
|
||||
"go_to_all" : "ver Todos",
|
||||
"go_to_starred" : "ver Destacados",
|
||||
"feed_search" : "navega a una suscripción al introducir su nombre"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"refresh" : "تازهسازی",
|
||||
"refresh_all" : "مجبورکردن تازهسازی همهٔ خوراکها",
|
||||
"sort_by_asc_desc" : "مرتبکردن بر اساس تاریخ بهصورت صعودی/نزولی",
|
||||
"sort_by_abc_zyx" : "Sort Alphabetically",
|
||||
"titles_only" : "فقط عنوانها",
|
||||
"expanded_view" : "نمای گسترشیافته",
|
||||
"mark_all_as_read" : "علامتگذاری تمامی مطالب بهعنوان خواندهشده",
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"refresh" : "Päivitä",
|
||||
"refresh_all" : "Pakota kaikkien syötteiden päivitys",
|
||||
"sort_by_asc_desc" : "Järjestä päivämäärän mukaan nousevasti/laskevasti",
|
||||
"sort_by_abc_zyx" : "Sort Alphabetically",
|
||||
"titles_only" : "Näytä vain otsikot",
|
||||
"expanded_view" : "Laajennettu näkymä",
|
||||
"mark_all_as_read" : "Merkitse kaikki luetuiksi",
|
||||
|
||||
@@ -34,24 +34,25 @@
|
||||
"parent" : "Parent"
|
||||
},
|
||||
"toolbar" : {
|
||||
"unread" : "Non-lus",
|
||||
"unread" : "Non lus",
|
||||
"all" : "Tous",
|
||||
"previous_entry" : "Article précédent",
|
||||
"next_entry" : "Article suivant",
|
||||
"refresh" : "Rafraîchir",
|
||||
"refresh_all" : "Force refresh all my feeds ",
|
||||
"refresh_all" : "Rafraîchir tous les flux",
|
||||
"sort_by_asc_desc" : "Trier par date croissante/décroissante",
|
||||
"sort_by_abc_zyx" : "Trier par ordre alphabétique",
|
||||
"titles_only" : "Titres uniquement",
|
||||
"expanded_view" : "Vue étendue",
|
||||
"mark_all_as_read" : "Tout marquer comme lu",
|
||||
"mark_all_older_12_hours" : "Items older than 12 hours ",
|
||||
"mark_all_older_day" : "Articles de plus d'un jour",
|
||||
"mark_all_older_12_hours" : "Articles de plus de 12 heures",
|
||||
"mark_all_older_day" : "Articles de plus d'une journée",
|
||||
"mark_all_older_week" : "Articles de plus d'une semaine",
|
||||
"mark_all_older_two_weeks" : "Articles de plus d'un mois",
|
||||
"settings" : "Préférences",
|
||||
"profile" : "Profil",
|
||||
"admin" : "Administration",
|
||||
"about" : "A propos",
|
||||
"about" : "À propos",
|
||||
"logout" : "Déconnexion",
|
||||
"donate" : "Faire un don"
|
||||
},
|
||||
@@ -59,16 +60,16 @@
|
||||
"entry_source" : "sur",
|
||||
"entry_author" : "par ",
|
||||
"error_while_loading_feed" : "Erreur durant le chargement de ce flux",
|
||||
"keep_unread" : "Garder non-lu",
|
||||
"no_unread_items" : "n'a pas d'articles non-lus.",
|
||||
"keep_unread" : "Garder non lu",
|
||||
"no_unread_items" : "n'a pas d'articles non lus.",
|
||||
"mark_up_to_here" : "Marquer comme lu jusqu'ici",
|
||||
"search_for" : "searching for: ",
|
||||
"no_search_results" : "No match found for the requested keywords "
|
||||
"search_for" : "recherche : ",
|
||||
"no_search_results" : "Pas de résultats avec le terme indiqué."
|
||||
},
|
||||
"feedsearch" : {
|
||||
"hint" : "Tapez un nom de flux",
|
||||
"help" : "Utilisez la touche entrée pour sélectionner et les flèches pour naviguer",
|
||||
"result_prefix" : "Vos flux:"
|
||||
"result_prefix" : "Vos flux :"
|
||||
},
|
||||
"settings" : {
|
||||
"general" : {
|
||||
@@ -76,12 +77,12 @@
|
||||
"language" : "Langue",
|
||||
"language_contribute" : "Contribuer aux traductions",
|
||||
"show_unread" : "Afficher les flux et les catégories pour lesquels tout est déjà lu",
|
||||
"social_buttons" : "Afficher les boutons de partage sur réseaux sociaux",
|
||||
"scroll_marks" : "En mode de lecture étendu, marquer comme lu les éléments lorsque la fenêtre descend."
|
||||
"social_buttons" : "Afficher les boutons de partage sur les réseaux sociaux",
|
||||
"scroll_marks" : "En mode de lecture étendu, marquer les éléments comme lus lorsque la fenêtre descend."
|
||||
},
|
||||
"appearance" : "Apparence",
|
||||
"scroll_speed" : "Scrolling speed when navigating between entries (in milliseconds) ",
|
||||
"scroll_speed_help" : "set to 0 to disable ",
|
||||
"scroll_speed" : "Vitesse de défilement entre les entrées (en millisecondes) ",
|
||||
"scroll_speed_help" : "Mettez 0 pour désactiver",
|
||||
"theme" : "Thème",
|
||||
"submit_your_theme" : "Soumettez votre thème.",
|
||||
"custom_css" : "CSS personnelle"
|
||||
@@ -94,16 +95,16 @@
|
||||
"category" : "Catégorie",
|
||||
"position" : "Position",
|
||||
"last_refresh" : "Dernière mise à jour",
|
||||
"message" : "Last refresh message ",
|
||||
"message" : "Message de la dernière mise à jour ",
|
||||
"next_refresh" : "Prochaine mise à jour",
|
||||
"queued_for_refresh" : "En file d'attente",
|
||||
"feed_url" : "URL du flux",
|
||||
"generate_api_key_first" : "Générez une clé API dans votre profil d'abord.",
|
||||
"generate_api_key_first" : "Générez d'abord une clé API dans votre profil.",
|
||||
"unsubscribe" : "Se désabonner",
|
||||
"unsubscribe_confirmation" : "Are you sure you want to unsubscribe from this feed? ",
|
||||
"delete_category_confirmation" : "Are you sure you want to delete this category? ",
|
||||
"unsubscribe_confirmation" : "Êtes-vous sûr de vouloir vous désabonner de de flux ? ",
|
||||
"delete_category_confirmation" : "Êtes-vous sûr de vouloir supprimer cette catégorie ? ",
|
||||
"category_details" : "Détails de la catégorie",
|
||||
"tag_details" : "Tag details ",
|
||||
"tag_details" : "Détails du tag",
|
||||
"parent_category" : "Catégorie parente"
|
||||
},
|
||||
"profile" : {
|
||||
@@ -116,15 +117,15 @@
|
||||
"api_key" : "Clé API",
|
||||
"api_key_not_generated" : "Pas encore générée",
|
||||
"generate_new_api_key" : "Générer une nouvelle clé API",
|
||||
"generate_new_api_key_info" : "Changer de mot de passe va générer une nouvelle clé API",
|
||||
"generate_new_api_key_info" : "Changer de mot de passe générera une nouvelle clé API",
|
||||
"opml_export" : "Export du fichier OPML",
|
||||
"delete_account" : "Effacer le compte",
|
||||
"delete_account_confirmation" : "Delete your acount? There's no turning back! "
|
||||
"delete_account_confirmation" : "Êtes-vous sûr de vouloir supprimer définitivement votre compte ?"
|
||||
},
|
||||
"about" : {
|
||||
"rest_api" : {
|
||||
"value" : "API REST",
|
||||
"line1" : "CommaFeed utilise JAX-RS et AngularJS, donc une API REST est disponible.",
|
||||
"line1" : "CommaFeed utilise JAX-RS et AngularJS, une API REST est donc disponible.",
|
||||
"link_to_documentation" : "Lien vers la documentation."
|
||||
},
|
||||
"keyboard_shortcuts" : "Raccourcis clavier",
|
||||
@@ -145,7 +146,7 @@
|
||||
"subscribe_bookmarklet" : "Bookmarklet d'ajout d'abonnement",
|
||||
"subscribe_bookmarklet_asc" : "Du plus ancien au plus récent",
|
||||
"subscribe_bookmarklet_desc" : "Du plus récent au plus ancien",
|
||||
"next_unread_bookmarklet" : "Bookmarklet vers le prochain article non-lu"
|
||||
"next_unread_bookmarklet" : "Bookmarklet vers le prochain article non lu"
|
||||
},
|
||||
"translation" : {
|
||||
"value" : "Traduction",
|
||||
@@ -165,9 +166,9 @@
|
||||
"open_previous_feed" : "Sélectionner le flux ou la catégorie précédente",
|
||||
"open_close_current_entry" : "Ouvrir/fermer l'article courant",
|
||||
"open_current_entry_in_new_window" : "Ouvrir l'article courant dans une nouvelle fenêtre",
|
||||
"open_current_entry_in_new_window_background" : "Ouvrir l'article courant dans une nouvelle fenêtre en arrière plan",
|
||||
"open_current_entry_in_new_window_background" : "Ouvrir l'article courant dans une nouvelle fenêtre en arrière-plan",
|
||||
"star_unstar" : "Ajouter/enlever l'article courant des favoris",
|
||||
"mark_current_entry" : "Marquer comme lue/non-lue l'article courant",
|
||||
"mark_current_entry" : "Marquer comme lu/non lu l'article courant",
|
||||
"mark_all_as_read" : "Marquer tous les articles comme lus",
|
||||
"open_in_new_tab_mark_as_read" : "Ouvrir l'article courant dans une nouvelle fenêtre et marquer comme lu",
|
||||
"fullscreen" : "Activer/désactiver le mode plein-écran",
|
||||
@@ -177,4 +178,4 @@
|
||||
"feed_search" : "Naviguer vers un flux en entrant son nom"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
},
|
||||
"new_category" : {
|
||||
"name" : "Nome",
|
||||
"parent" : "Pai"
|
||||
"parent" : "Subcategoría de "
|
||||
},
|
||||
"toolbar" : {
|
||||
"unread" : "Sen Ler",
|
||||
@@ -39,12 +39,13 @@
|
||||
"previous_entry" : "Entrada Anterior",
|
||||
"next_entry" : "Próxima Entrada",
|
||||
"refresh" : "Actualizar",
|
||||
"refresh_all" : "Force refresh all my feeds ",
|
||||
"refresh_all" : "Forzar a actualización de todas as fontes ",
|
||||
"sort_by_asc_desc" : "Ordenar por data asc/desc",
|
||||
"sort_by_abc_zyx" : "Sort Alphabetically",
|
||||
"titles_only" : "Só títulos",
|
||||
"expanded_view" : "Vista expandida",
|
||||
"mark_all_as_read" : "Marcar todos como lidos",
|
||||
"mark_all_older_12_hours" : "Items older than 12 hours ",
|
||||
"mark_all_older_12_hours" : "Elementos anteriores a 12 h. ",
|
||||
"mark_all_older_day" : "Artigos anteriores a un día",
|
||||
"mark_all_older_week" : "Artigos de máis de unha semana",
|
||||
"mark_all_older_two_weeks" : "Artigos de máis de dúas semanas",
|
||||
@@ -56,14 +57,14 @@
|
||||
"donate" : "Doar"
|
||||
},
|
||||
"view" : {
|
||||
"entry_source" : "from ",
|
||||
"entry_author" : "by ",
|
||||
"entry_source" : "desde ",
|
||||
"entry_author" : "por ",
|
||||
"error_while_loading_feed" : "Erro mentras se cargaba esta fonte",
|
||||
"keep_unread" : "Gardar non lidos",
|
||||
"no_unread_items" : "non ten elementos sen ler.",
|
||||
"mark_up_to_here" : "Mark as read up to here ",
|
||||
"search_for" : "searching for: ",
|
||||
"no_search_results" : "No match found for the requested keywords "
|
||||
"mark_up_to_here" : "Marcar como lidos ate aquí ",
|
||||
"search_for" : "buscando por: ",
|
||||
"no_search_results" : "Sen coincidencias para as palabras introducidas "
|
||||
},
|
||||
"feedsearch" : {
|
||||
"hint" : "Escriba unha suscrición...",
|
||||
@@ -80,8 +81,8 @@
|
||||
"scroll_marks" : "En vista expandida, o desplazamento polas entradas márcaas como lidas."
|
||||
},
|
||||
"appearance" : "Aspecto",
|
||||
"scroll_speed" : "Scrolling speed when navigating between entries (in milliseconds) ",
|
||||
"scroll_speed_help" : "set to 0 to disable ",
|
||||
"scroll_speed" : "Velocidade de desplazamento navegando entre entradas (en milisegundos) ",
|
||||
"scroll_speed_help" : "escriba 0 para deshabilitar ",
|
||||
"theme" : "Decorado",
|
||||
"submit_your_theme" : "Envíe o seu decorado",
|
||||
"custom_css" : "CSS Personalizado"
|
||||
@@ -89,21 +90,21 @@
|
||||
"details" : {
|
||||
"feed_details" : "Detalles de fontes",
|
||||
"url" : "URL",
|
||||
"website" : "Website ",
|
||||
"website" : "Sitio web ",
|
||||
"name" : "Nome",
|
||||
"category" : "Categoría",
|
||||
"position" : "Position ",
|
||||
"position" : "Posición ",
|
||||
"last_refresh" : "Última actualización",
|
||||
"message" : "Last refresh message ",
|
||||
"message" : "Última mensaxe da actualización ",
|
||||
"next_refresh" : "Próxima actualización",
|
||||
"queued_for_refresh" : "En cola para actualizar",
|
||||
"feed_url" : "URL da fonte",
|
||||
"generate_api_key_first" : "Antes debes xerar unha chave API no teu perfil.",
|
||||
"unsubscribe" : "Rematar suscripción",
|
||||
"unsubscribe_confirmation" : "Are you sure you want to unsubscribe from this feed? ",
|
||||
"delete_category_confirmation" : "Are you sure you want to delete this category? ",
|
||||
"unsubscribe_confirmation" : "Seguro que queres desuscribirte de esta fonte? ",
|
||||
"delete_category_confirmation" : "Seguro que queres eliminar esta categoría? ",
|
||||
"category_details" : "Detalles da categoría",
|
||||
"tag_details" : "Tag details ",
|
||||
"tag_details" : "Detalles da etiqueta ",
|
||||
"parent_category" : "Categoría principal"
|
||||
},
|
||||
"profile" : {
|
||||
@@ -119,7 +120,7 @@
|
||||
"generate_new_api_key_info" : "Ao cambiar o contrasinal xerarase unha nova chave API",
|
||||
"opml_export" : "Exportación de OPML",
|
||||
"delete_account" : "Eliminar conta",
|
||||
"delete_account_confirmation" : "Delete your acount? There's no turning back! "
|
||||
"delete_account_confirmation" : "Eliminar conta? Non hai volta atrás! "
|
||||
},
|
||||
"about" : {
|
||||
"rest_api" : {
|
||||
@@ -158,7 +159,7 @@
|
||||
"open_next_entry" : "abrir próxima entrada",
|
||||
"open_previous_entry" : "abrir entrada anterior",
|
||||
"spacebar" : "space/shift+space ",
|
||||
"move_page_down_up" : "moves the page down/up ",
|
||||
"move_page_down_up" : "move a páxina arriba/abaixo ",
|
||||
"focus_next_entry" : "Establecer o foco na próxima entrada sen abrila",
|
||||
"focus_previous_entry" : "Establecer o foco na entrada anterior sen abrila",
|
||||
"open_next_feed" : "abrir a seguinte fonte ou categoría",
|
||||
@@ -170,11 +171,11 @@
|
||||
"mark_current_entry" : "marcar como lida/non lida a entrada actual",
|
||||
"mark_all_as_read" : "marcar todas as entradas como lidas",
|
||||
"open_in_new_tab_mark_as_read" : "abrir entrada nunha nova lapela e marcar como lida",
|
||||
"fullscreen" : "toggle full screen mode ",
|
||||
"font_size" : "increase/decrease font size of the current entry ",
|
||||
"go_to_all" : "go to the All view ",
|
||||
"go_to_starred" : "go to the Starred view ",
|
||||
"fullscreen" : "habilita a pantalla completa ",
|
||||
"font_size" : "aumenta/diminúe o tamaño da letra da entrada activa ",
|
||||
"go_to_all" : "ir a vista TODOS",
|
||||
"go_to_starred" : "ir a vista Destacados ",
|
||||
"feed_search" : "navegue ate unha suscrición introducindo o nome da suscrición"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"refresh" : "واجأری",
|
||||
"refresh_all" : "Force refresh all my feeds ",
|
||||
"sort_by_asc_desc" : "تاریخˇ سر دچئن",
|
||||
"sort_by_abc_zyx" : "Sort Alphabetically",
|
||||
"titles_only" : "خالی تیتران",
|
||||
"expanded_view" : "واشاده نما",
|
||||
"mark_all_as_read" : "همهته مطالبه چاکون بخانده",
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"refresh" : "Frissítés",
|
||||
"refresh_all" : "Force refresh all my feeds ",
|
||||
"sort_by_asc_desc" : "Rendezés időrend szerint",
|
||||
"sort_by_abc_zyx" : "Sort Alphabetically",
|
||||
"titles_only" : "Csak cím",
|
||||
"expanded_view" : "Részletes nézet",
|
||||
"mark_all_as_read" : "Az összes megjelölése olvasottként",
|
||||
|
||||
183
src/main/app/i18n/id.js
Normal file
183
src/main/app/i18n/id.js
Normal file
@@ -0,0 +1,183 @@
|
||||
{
|
||||
"global" : {
|
||||
"save" : "Simpan",
|
||||
"cancel" : "Batal",
|
||||
"delete" : "Hapus",
|
||||
"required" : "Diminta",
|
||||
"download" : "Unduh",
|
||||
"link" : "Tautan",
|
||||
"bookmark" : "Penanda halaman buku",
|
||||
"close" : "Tutup",
|
||||
"tags" : "Penanda"
|
||||
},
|
||||
"tree" : {
|
||||
"subscribe" : "Berlangganan",
|
||||
"import" : "Impor",
|
||||
"new_category" : "Kategori Baru",
|
||||
"all" : "Semua",
|
||||
"starred" : "Diutamakan"
|
||||
},
|
||||
"subscribe" : {
|
||||
"feed_url" : "Umpan URL",
|
||||
"feed_name" : "Nama Umpan",
|
||||
"category" : "Kategori"
|
||||
},
|
||||
"import" : {
|
||||
"google_reader_prefix" : "Izinkan saya mengimpor umpan Anda dari kepunyaan Anda ",
|
||||
"google_reader_suffix" : " akun.",
|
||||
"google_download" : "Atau, unggah berkas subscriptions.xml Anda.",
|
||||
"google_download_link" : "Unggah dari sini.",
|
||||
"xml_file" : "Berkas OPML"
|
||||
},
|
||||
"new_category" : {
|
||||
"name" : "Nama",
|
||||
"parent" : "Induk"
|
||||
},
|
||||
"toolbar" : {
|
||||
"unread" : "Belum dibaca",
|
||||
"all" : "Semua",
|
||||
"previous_entry" : "Catatan sebelumnya",
|
||||
"next_entry" : "Catatan selanjutnya",
|
||||
"refresh" : "Segarkan",
|
||||
"refresh_all" : "Memaksa menyegarkan semua umpan saya",
|
||||
"sort_by_asc_desc" : "Urutkan menurut tanggal asc/desc",
|
||||
"sort_by_abc_zyx" : "Sort Alphabetically",
|
||||
"titles_only" : "Hanya Judul",
|
||||
"expanded_view" : "Penglihatan diperluas",
|
||||
"mark_all_as_read" : "Tandai semua sebagai telah dibaca",
|
||||
"mark_all_older_12_hours" : "Butir lebih lama 12 jam",
|
||||
"mark_all_older_day" : "Butir lebih lama sehari",
|
||||
"mark_all_older_week" : "Butir lebih lama seminggu",
|
||||
"mark_all_older_two_weeks" : "Butir lebih lama dua minggu",
|
||||
"settings" : "Pengaturan",
|
||||
"profile" : "Profil",
|
||||
"admin" : "Admin",
|
||||
"about" : "Tentang",
|
||||
"logout" : "Keluar",
|
||||
"donate" : "Donasi"
|
||||
},
|
||||
"view" : {
|
||||
"entry_source" : "dari ",
|
||||
"entry_author" : "oleh ",
|
||||
"error_while_loading_feed" : "Galat saat memuat umpan ini",
|
||||
"keep_unread" : "Tetapkan belum dibaca",
|
||||
"no_unread_items" : "Tidak ada yang butir yang belum dibaca.",
|
||||
"mark_up_to_here" : "Tandai sebagai dibaca di sini",
|
||||
"search_for" : "mencari: ",
|
||||
"no_search_results" : "Tidak ditemukan kata kunci yang sesuai dengan permintaan"
|
||||
},
|
||||
"feedsearch" : {
|
||||
"hint" : "Ketik sebuah langganan...",
|
||||
"help" : "Gunakan tombol enter untuk memilih dan tombol panah untuk navigasi.",
|
||||
"result_prefix" : "Langganan Anda:"
|
||||
},
|
||||
"settings" : {
|
||||
"general" : {
|
||||
"value" : "Umum",
|
||||
"language" : "Bahasa",
|
||||
"language_contribute" : "Kontribusi dengan terjemahan",
|
||||
"show_unread" : "Menampilkan umpan dan kategori tanpa catatan belum dibaca",
|
||||
"social_buttons" : "Menampilkan tombol sosial berbagi",
|
||||
"scroll_marks" : "Di penglihatan luas, menggulir melalui catatan menandakan sebagai telah dibaca"
|
||||
},
|
||||
"appearance" : "Penampilan",
|
||||
"scroll_speed" : "Kecepatan menggulir ketika menavigasi antar catatan (dalam milidetik)",
|
||||
"scroll_speed_help" : "setel ke 0 untuk menonaktifkan",
|
||||
"theme" : "Tema",
|
||||
"submit_your_theme" : "Mengajukan tema Anda",
|
||||
"custom_css" : "Ubah CSS"
|
||||
},
|
||||
"details" : {
|
||||
"feed_details" : "Rincian Umpan",
|
||||
"url" : "URL",
|
||||
"website" : "Situs",
|
||||
"name" : "Nama",
|
||||
"category" : "Kategori",
|
||||
"position" : "Posisi",
|
||||
"last_refresh" : "Penyegaran terakhir",
|
||||
"message" : "Menyegarkan pesan terakhir",
|
||||
"next_refresh" : "Penyegaran selanjutnya",
|
||||
"queued_for_refresh" : "Antri untuk penyegaran",
|
||||
"feed_url" : "Umpan URL",
|
||||
"filtering_expression" : "Penyaring ekspresi",
|
||||
"filtering_expression_help" : "Jika tidak kosong, sebuah ekspresi mengevaluasi ke 'benar' atau 'salah'. Jika salah, catatan baru untuk umpan ini akan ditandai sebagai telah dibaca secara otomatis. \nVariabel yang tersedia adalah 'judul', 'konten', 'url' 'penulis' dan 'kategori' dan konten mereka dikonversi dari huruf kecil ke perbandingan string yang mudah. \nContoh: url.contains('youtube') or (author eq 'athou' and title.contains('github'). \nSintaksis lengkap tersedia di <a href='http://commons.apache.org/proper/commons-jexl/reference/syntax.html' target='_blank'>here</a>.",
|
||||
"generate_api_key_first" : "Menghasilkan sebuah kunci API di profil Anda terlebih dahulu.",
|
||||
"unsubscribe" : "Berhenti berlangganan",
|
||||
"unsubscribe_confirmation" : "Apakah Anda yakin ingin berhenti berlangganan dari umpan ini?",
|
||||
"delete_category_confirmation" : "Apakah Anda yakin ingin menghapus dari kategori ini?",
|
||||
"category_details" : "rincian Kategori",
|
||||
"tag_details" : "rincian Penanda",
|
||||
"parent_category" : "kategori Induk"
|
||||
},
|
||||
"profile" : {
|
||||
"user_name" : "nama Pengguna",
|
||||
"email" : "Surel",
|
||||
"change_password" : "Ganti kata sandi",
|
||||
"confirm_password" : "Konfirmasi kata sandi",
|
||||
"minimum_6_chars" : "Minimal 6 karakter",
|
||||
"passwords_do_not_match" : "Kata sandi tidak sesuai",
|
||||
"api_key" : "kunci API",
|
||||
"api_key_not_generated" : "Belum menghasilkan",
|
||||
"generate_new_api_key" : "Hasilkan kunci API baru",
|
||||
"generate_new_api_key_info" : "Mengganti kata sandi akan menghasilkan sebuah kunci API baru",
|
||||
"opml_export" : "ekspor OPML",
|
||||
"delete_account" : "Hapus akun",
|
||||
"delete_account_confirmation" : "Hapus akun Anda? Hal ini tidak dapat dikembalikan!"
|
||||
},
|
||||
"about" : {
|
||||
"rest_api" : {
|
||||
"value" : "REST API",
|
||||
"line1" : "CommaFeed dibangun di atas JAX-RS dan AngularJS. Dengan demikian, tersedia sebuah REST API.",
|
||||
"link_to_documentation" : "Tautan menuju dokumentasi."
|
||||
},
|
||||
"keyboard_shortcuts" : "pintasan Papanketik",
|
||||
"version" : "versi CommaFeed",
|
||||
"line1_prefix" : "CommaFeed adalah suatu proyek open-source. Sumber di ",
|
||||
"line1_suffix" : ".",
|
||||
"line2_prefix" : "Jika Anda mengalami sebuah isu, silahkan laporkan pada halaman isu ",
|
||||
"line2_suffix" : " proyek.",
|
||||
"line3" : "Jika Anda menyukai proyek ini, silahkan mempertimbangkan suatu donasi untuk mendukung pengembang dan membantu menutupi biaya online situs ini.",
|
||||
"line4" : "Untuk Anda yang lebih suka bitcoin, alamatnya di sini",
|
||||
"goodies" : {
|
||||
"value" : "Bingkisan",
|
||||
"android_app" : "Android app",
|
||||
"subscribe_url" : "URL Langganan",
|
||||
"chrome_extension" : "ekstensi Chrome",
|
||||
"firefox_extension" : "ekstensi Firefox",
|
||||
"opera_extension" : "ekstensi Opera",
|
||||
"subscribe_bookmarklet" : "Tambahkan bookmarklet langganan(klik)",
|
||||
"subscribe_bookmarklet_asc" : "Terlama dahulu",
|
||||
"subscribe_bookmarklet_desc" : "Terbaru dahulu",
|
||||
"next_unread_bookmarklet" : "Butir bookmarklet selanjutnya yang belum dibaca (seret ke batang penanda halaman buku)"
|
||||
},
|
||||
"translation" : {
|
||||
"value" : "Terjemahan",
|
||||
"message" : "Kami membutuhkan bantuan Anda untuk menterjemahkan CommaFeed.",
|
||||
"link" : "Lihat bagaimana berkontribusi dengan terjemahan."
|
||||
},
|
||||
"announcements" : "Pengumuman",
|
||||
"shortcuts" : {
|
||||
"mouse_middleclick" : "klik tengah tetikus",
|
||||
"open_next_entry" : "buka catatan selanjutnya",
|
||||
"open_previous_entry" : "buka catatan sebelumnya",
|
||||
"spacebar" : "spasi/shift+spasi",
|
||||
"move_page_down_up" : "pindah halaman bawah/atas",
|
||||
"focus_next_entry" : "setel fokus pada catatan selanjutnya tanpa membukanya",
|
||||
"focus_previous_entry" : "setel fokus pada catatan sebelumnya tanpa membukanya",
|
||||
"open_next_feed" : "buka umpan atau kategori selanjutnya",
|
||||
"open_previous_feed" : "buka umpan atau kategori sebelumnya",
|
||||
"open_close_current_entry" : "buka/tutup catatan saat ini",
|
||||
"open_current_entry_in_new_window" : "buka catatan saat ini di sebuah jendela baru",
|
||||
"open_current_entry_in_new_window_background" : "buka catatan saat ini di sebuah jendela baru pada latar",
|
||||
"star_unstar" : "tanda bintang/tidak catatan saat ini",
|
||||
"mark_current_entry" : "tandai sebagai telah dibaca/belum catatan saat ini",
|
||||
"mark_all_as_read" : "tandai semua catatan sebagai telah dibaca",
|
||||
"open_in_new_tab_mark_as_read" : "buka catatan di tab baru dan tandai sebagai telah dibaca",
|
||||
"fullscreen" : "beralih modus layar penuh",
|
||||
"font_size" : "tingkatkan/turunkan ukuran huruf dari catatan saat ini",
|
||||
"go_to_all" : "menuju ke lihat Semua",
|
||||
"go_to_starred" : "menuju ke lihat Tanda Bintang",
|
||||
"feed_search" : "navigasi ke langganan dengan memasukkan nama langganan"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +1,51 @@
|
||||
{
|
||||
"global" : {
|
||||
"save" : "Salva",
|
||||
"cancel" : "Cancella",
|
||||
"cancel" : "Annulla",
|
||||
"delete" : "Elimina",
|
||||
"required" : "Required",
|
||||
"required" : "Richiesto",
|
||||
"download" : "Download",
|
||||
"link" : "Link",
|
||||
"bookmark" : "Segnalibro ",
|
||||
"bookmark" : "Segnalibro",
|
||||
"close" : "Chiudi",
|
||||
"tags" : "Tags "
|
||||
"tags" : "Tag"
|
||||
},
|
||||
"tree" : {
|
||||
"subscribe" : "Iscriviti",
|
||||
"import" : "Importa",
|
||||
"new_category" : "Nuova categoria",
|
||||
"all" : "Tutto",
|
||||
"all" : "Tutti",
|
||||
"starred" : "Preferiti"
|
||||
},
|
||||
"subscribe" : {
|
||||
"feed_url" : "Feed URL",
|
||||
"feed_name" : "Nome Feed",
|
||||
"feed_url" : "URL feed",
|
||||
"feed_name" : "Nome feed",
|
||||
"category" : "Categoria"
|
||||
},
|
||||
"import" : {
|
||||
"google_reader_prefix" : "Importa i tuoi feed dal tuo ",
|
||||
"google_reader_suffix" : " account.",
|
||||
"google_download" : "Oppure, carica il tuo subscriptions.xml",
|
||||
"google_download_link" : "Scaricalo da qui",
|
||||
"xml_file" : "OPML File"
|
||||
"google_reader_prefix" : "Permettimi di importare i feed dal tuo account ",
|
||||
"google_reader_suffix" : ".",
|
||||
"google_download" : "Oppure carica il tuo file subscriptions.xml.",
|
||||
"google_download_link" : "Puoi scaricalo da qui.",
|
||||
"xml_file" : "File OPML"
|
||||
},
|
||||
"new_category" : {
|
||||
"name" : "Nome",
|
||||
"parent" : "Parent"
|
||||
"parent" : "Gruppo"
|
||||
},
|
||||
"toolbar" : {
|
||||
"unread" : "Non letti",
|
||||
"all" : "Tutti",
|
||||
"previous_entry" : "Precedente",
|
||||
"next_entry" : "Successivo",
|
||||
"refresh" : "Ricarica",
|
||||
"refresh_all" : "Force refresh all my feeds ",
|
||||
"sort_by_asc_desc" : "Sort by date asc/desc",
|
||||
"titles_only" : "Solo titoli",
|
||||
"refresh" : "Aggiorna",
|
||||
"refresh_all" : "Forza l'aggiornamento di tutti i feed",
|
||||
"sort_by_asc_desc" : "Ordina per data crescente/decrescente",
|
||||
"sort_by_abc_zyx" : "Sort Alphabetically",
|
||||
"titles_only" : "Solo i titoli",
|
||||
"expanded_view" : "Espandi",
|
||||
"mark_all_as_read" : "Contrassegna tutto come già letto",
|
||||
"mark_all_older_12_hours" : "Items older than 12 hours ",
|
||||
"mark_all_as_read" : "Segna tutti come già letti",
|
||||
"mark_all_older_12_hours" : "Elementi più vecchi di 12 ore",
|
||||
"mark_all_older_day" : "Elementi più vecchi di un giorno",
|
||||
"mark_all_older_week" : "Elementi più vecchi di una settimana",
|
||||
"mark_all_older_two_weeks" : "Elementi più vecchi di due settimane",
|
||||
@@ -52,129 +53,131 @@
|
||||
"profile" : "Profilo",
|
||||
"admin" : "Admin",
|
||||
"about" : "Informazioni",
|
||||
"logout" : "Logout",
|
||||
"logout" : "Esci",
|
||||
"donate" : "Dona"
|
||||
},
|
||||
"view" : {
|
||||
"entry_source" : "from ",
|
||||
"entry_author" : "by ",
|
||||
"error_while_loading_feed" : "Si è verificato un errore, mentre caricavo il feed",
|
||||
"keep_unread" : "Mantiene come da leggere",
|
||||
"no_unread_items" : "Non ci sono elementi da leggere.",
|
||||
"mark_up_to_here" : "Mark as read up to here ",
|
||||
"search_for" : "searching for: ",
|
||||
"no_search_results" : "No match found for the requested keywords "
|
||||
"entry_source" : "da",
|
||||
"entry_author" : "di",
|
||||
"error_while_loading_feed" : "Si è verificato un errore durante il caricamento del feed",
|
||||
"keep_unread" : "Mantieni come da leggere",
|
||||
"no_unread_items" : "non contiene elementi da leggere",
|
||||
"mark_up_to_here" : "Segna come letto fin qui",
|
||||
"search_for" : "Cerca: ",
|
||||
"no_search_results" : "Nessun risultato per le parole chiave cercate"
|
||||
},
|
||||
"feedsearch" : {
|
||||
"hint" : "Type in a subscription... ",
|
||||
"help" : "Use the return key to select and arrow keys to navigate. ",
|
||||
"result_prefix" : "Le tue sottoscrizioni"
|
||||
"hint" : "Digita il nome di una sottoscrizione... ",
|
||||
"help" : "Usa il tasto Invio per selezionare e le frecce per navigare.",
|
||||
"result_prefix" : "Le tue sottoscrizioni:"
|
||||
},
|
||||
"settings" : {
|
||||
"general" : {
|
||||
"value" : "Generali",
|
||||
"language" : "Lingua",
|
||||
"language_contribute" : "Contribuisci con le traduzioni",
|
||||
"show_unread" : "Show feeds and categories with no unread entries",
|
||||
"social_buttons" : "Visualizza i social button",
|
||||
"scroll_marks" : "Marca come letto quando scorri"
|
||||
"language_contribute" : "Contribuisci alle traduzioni",
|
||||
"show_unread" : "Mostra i feed e le categorie con voci non lette",
|
||||
"social_buttons" : "Mostra i pulsanti di condivisione social",
|
||||
"scroll_marks" : "In vista estesa, segna come lette le voci che scorri"
|
||||
},
|
||||
"appearance" : "Appearance ",
|
||||
"scroll_speed" : "Scrolling speed when navigating between entries (in milliseconds) ",
|
||||
"scroll_speed_help" : "set to 0 to disable ",
|
||||
"theme" : "Tema ",
|
||||
"submit_your_theme" : "Sottoponi il tuo tema ",
|
||||
"custom_css" : "Css modificato"
|
||||
"appearance" : "Aspetto",
|
||||
"scroll_speed" : "Velocità di scorrimento quando navighi tra i feed (in millisecondi)",
|
||||
"scroll_speed_help" : "Imposta su 0 per disabilitare",
|
||||
"theme" : "Tema",
|
||||
"submit_your_theme" : "Sottoponi il tuo tema",
|
||||
"custom_css" : "CSS personalizzato"
|
||||
},
|
||||
"details" : {
|
||||
"feed_details" : "Dettagli feed",
|
||||
"url" : "URL",
|
||||
"website" : "Website ",
|
||||
"website" : "Sito web",
|
||||
"name" : "Nome",
|
||||
"category" : "Categoria",
|
||||
"position" : "Posizione ",
|
||||
"position" : "Posizione",
|
||||
"last_refresh" : "Ultimo aggiornamento",
|
||||
"message" : "Last refresh message ",
|
||||
"next_refresh" : "Next refresh ",
|
||||
"queued_for_refresh" : "In attesa per l'aggiornamento ",
|
||||
"feed_url" : "Feed URL",
|
||||
"generate_api_key_first" : "Generate an API key in your profile first.",
|
||||
"unsubscribe" : "Annulla l\"'\"iscrizione",
|
||||
"unsubscribe_confirmation" : "Are you sure you want to unsubscribe from this feed? ",
|
||||
"delete_category_confirmation" : "Are you sure you want to delete this category? ",
|
||||
"message" : "Ultimo messaggio di aggiornamento",
|
||||
"next_refresh" : "Prossimo aggiornamento",
|
||||
"queued_for_refresh" : "In coda per l'aggiornamento",
|
||||
"feed_url" : "URL feed",
|
||||
"filtering_expression" : "Espressione filtro",
|
||||
"filtering_expression_help" : "Quando non è vuota, l'espressione viene applicata a ogni nuovo elemento e valutata come 'vera' o 'falsa'. Se falsa, l'elemento verrà segnato automaticamente come letto.\nLe variabili accettate sono 'title' (titolo), 'content' (contenuto), 'url', 'author' (autore) e 'categories' (categorie); il loro contenuto è convertito in minuscolo per facilitarne il confronto.\nEsempio: url.contains('youtube') or (author eq 'athou' and title.contains('github')).\nLa sintassi completa è disponibile <a href='http://commons.apache.org/proper/commons-jexl/reference/syntax.html' target='_blank'>qui</a> (in inglese).",
|
||||
"generate_api_key_first" : "Genera prima una chiave API nelle impostazioni del tuo profilo.",
|
||||
"unsubscribe" : "Disiscriviti",
|
||||
"unsubscribe_confirmation" : "Sei sicuro di voler annullare la sottoscrizione al feed?",
|
||||
"delete_category_confirmation" : "Sei sicuro di voler eliminare questa categoria?",
|
||||
"category_details" : "Dettagli categoria",
|
||||
"tag_details" : "Tag details ",
|
||||
"parent_category" : "Parent category"
|
||||
"tag_details" : "Dettagli tag",
|
||||
"parent_category" : "Categoria principale"
|
||||
},
|
||||
"profile" : {
|
||||
"user_name" : "User name",
|
||||
"user_name" : "Nome utente",
|
||||
"email" : "E-mail",
|
||||
"change_password" : "Cambia password",
|
||||
"confirm_password" : "Conferma password",
|
||||
"minimum_6_chars" : "Minimo 6 caratteri",
|
||||
"passwords_do_not_match" : "Le password non corrispondono",
|
||||
"api_key" : "API key",
|
||||
"api_key_not_generated" : "Non generata ancora",
|
||||
"api_key" : "chiave API",
|
||||
"api_key_not_generated" : "Non ancora generata",
|
||||
"generate_new_api_key" : "Genera una nuova chiave API",
|
||||
"generate_new_api_key_info" : "Cambiando la password sarà generata una nuova chiave API",
|
||||
"opml_export" : "Esporta OPML",
|
||||
"delete_account" : "Elimina account",
|
||||
"delete_account_confirmation" : "Delete your acount? There's no turning back! "
|
||||
"delete_account_confirmation" : "Vuoi eliminare il tuo account? Non si può tornare indietro!"
|
||||
},
|
||||
"about" : {
|
||||
"rest_api" : {
|
||||
"value" : "REST API",
|
||||
"line1" : "CommaFeed is built on top of JAX-RS and AngularJS. As such, a REST API is available.",
|
||||
"line1" : "CommaFeed è basato su JAX-RS e AngularJS. Pertanto è disponibile una REST API.",
|
||||
"link_to_documentation" : "Link alla documentazione."
|
||||
},
|
||||
"keyboard_shortcuts" : "Scorciatoie da tastiera",
|
||||
"version" : "CommaFeed version ",
|
||||
"line1_prefix" : "Commefeed è un progetto open-source. I codici sono hostati su ",
|
||||
"keyboard_shortcuts" : "Scorciatoie da tastiera",
|
||||
"version" : "Versione di CommaFeed",
|
||||
"line1_prefix" : "CommaFeed è un progetto open source. Trovi i sorgenti su ",
|
||||
"line1_suffix" : ".",
|
||||
"line2_prefix" : "Se hai qualche problema, segnalalo sulla pagina del ",
|
||||
"line2_suffix" : " progetto.",
|
||||
"line3" : "Se ti piace il progetto, prendi in considerazione una donazione per supportare lo sviluppatore e contribuire a coprire i costi di mantenenimento di questo sito on-line.",
|
||||
"line4" : "Per chi preferisce Bitcoin, questo è l\"'\"indirizzo ",
|
||||
"line2_prefix" : "Se hai qualche problema, segnalalo sulla pagina del progetto ",
|
||||
"line2_suffix" : ".",
|
||||
"line3" : "Se ti piace questo progetto, considera una donazione per supportare lo sviluppatore e aiutare a coprire i costi di manutenzione di questo sito.",
|
||||
"line4" : "Se preferisci Bitcoin, questo è l'indirizzo",
|
||||
"goodies" : {
|
||||
"value" : "Goodies",
|
||||
"android_app" : "Android app ",
|
||||
"subscribe_url" : "Subscribe URL ",
|
||||
"chrome_extension" : "Estenzione per Chrome ",
|
||||
"value" : "Cose che potrebbero interessarti",
|
||||
"android_app" : "Applicazione Android",
|
||||
"subscribe_url" : "Sottoscrivi URL",
|
||||
"chrome_extension" : "Estensione per Chrome",
|
||||
"firefox_extension" : "Estensione per Firefox",
|
||||
"opera_extension" : "Estensione per Opera",
|
||||
"subscribe_bookmarklet" : "Add subscription bookmarklet (click) ",
|
||||
"subscribe_bookmarklet_asc" : "Oldest first ",
|
||||
"subscribe_bookmarklet_desc" : "Newest first ",
|
||||
"next_unread_bookmarklet" : "Next unread item bookmarklet (drag to bookmark bar) "
|
||||
"subscribe_bookmarklet" : "Aggiungi la sottoscrizione ai segnalibri (clicca)",
|
||||
"subscribe_bookmarklet_asc" : "Prima i vecchi",
|
||||
"subscribe_bookmarklet_desc" : "Prima i recenti",
|
||||
"next_unread_bookmarklet" : "Bookmarklet al prossimo elemento da leggere (trascinalo nella barra dei segnalibri)"
|
||||
},
|
||||
"translation" : {
|
||||
"value" : "Traduzioni",
|
||||
"message" : "Abbiamo bisogno del tuo aiuto per tradurre CommaFeed.",
|
||||
"link" : "Vedi come aiutare con le traduzioni."
|
||||
"link" : "Scopri come aiutarci nella traduzioni."
|
||||
},
|
||||
"announcements" : "Annunci",
|
||||
"shortcuts" : {
|
||||
"mouse_middleclick" : "mouse middleclick",
|
||||
"open_next_entry" : "open next entry",
|
||||
"open_previous_entry" : "open previous entry",
|
||||
"spacebar" : "space/shift+space ",
|
||||
"move_page_down_up" : "moves the page down/up ",
|
||||
"focus_next_entry" : "set focus on next entry without opening it ",
|
||||
"focus_previous_entry" : "set focus on previous entry without opening it ",
|
||||
"open_next_feed" : "open next feed or category ",
|
||||
"open_previous_feed" : "open previous feed or category ",
|
||||
"open_close_current_entry" : "open/close current entry",
|
||||
"open_current_entry_in_new_window" : "open current entry in a new window",
|
||||
"open_current_entry_in_new_window_background" : "open current entry in a new window in the background ",
|
||||
"star_unstar" : "star/unstar current entry",
|
||||
"mark_current_entry" : "mark as read/unread current entry",
|
||||
"mark_all_as_read" : "mark all entries as read",
|
||||
"open_in_new_tab_mark_as_read" : "open entry in new tab and mark as read",
|
||||
"fullscreen" : "toggle full screen mode ",
|
||||
"font_size" : "increase/decrease font size of the current entry ",
|
||||
"go_to_all" : "go to the All view ",
|
||||
"go_to_starred" : "go to the Starred view ",
|
||||
"feed_search" : "navigate to a subscription by entering the subscription name "
|
||||
"mouse_middleclick" : "click centrale del mouse",
|
||||
"open_next_entry" : "apri successivo",
|
||||
"open_previous_entry" : "apri precedente",
|
||||
"spacebar" : "SPAZIO/MAIUSC+SPAZIO",
|
||||
"move_page_down_up" : "muove la pagina in su/giù",
|
||||
"focus_next_entry" : "metti a fuoco l'elemento successivo senza aprirlo",
|
||||
"focus_previous_entry" : "metti a fuoco l'elemento precedente senza aprirlo",
|
||||
"open_next_feed" : "apri il prossimo feed o categoria",
|
||||
"open_previous_feed" : "apri il feed o la categoria precedente",
|
||||
"open_close_current_entry" : "apri/chiudi la voce corrente",
|
||||
"open_current_entry_in_new_window" : "apri la voce corrente in una nuova finestra",
|
||||
"open_current_entry_in_new_window_background" : "apri la voce corrente in una nuova finestra in secondo piano",
|
||||
"star_unstar" : "metti/togli la tua preferenza alla voce corrente",
|
||||
"mark_current_entry" : "segna la voce corrente come letta/non letta",
|
||||
"mark_all_as_read" : "segna tutte le voci come lette",
|
||||
"open_in_new_tab_mark_as_read" : "apri voce in un nuovo tab e segna come letta",
|
||||
"fullscreen" : "commuta la modalità a schermo intero",
|
||||
"font_size" : "aumenta/decrementa la dimensione del font per la voce corrente",
|
||||
"go_to_all" : "vai alla vista Tutti",
|
||||
"go_to_starred" : "vai alla vista Preferiti",
|
||||
"feed_search" : "raggiungi una sottoscrizione scrivendo il suo nome"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"refresh" : "更新",
|
||||
"refresh_all" : "全てのフィードを更新",
|
||||
"sort_by_asc_desc" : "昇順/降順にソート",
|
||||
"sort_by_abc_zyx" : "Sort Alphabetically",
|
||||
"titles_only" : "タイトルのみ",
|
||||
"expanded_view" : "拡張ビュー",
|
||||
"mark_all_as_read" : "全て既読にする",
|
||||
|
||||
@@ -4,23 +4,23 @@
|
||||
"cancel" : "취소",
|
||||
"delete" : "삭제",
|
||||
"required" : "필수",
|
||||
"download" : "Download ",
|
||||
"link" : "Link ",
|
||||
"bookmark" : "Bookmark ",
|
||||
"close" : "Close ",
|
||||
"tags" : "Tags "
|
||||
"download" : "다운로드",
|
||||
"link" : "링크",
|
||||
"bookmark" : "북마크",
|
||||
"close" : "닫기 ",
|
||||
"tags" : "태그 "
|
||||
},
|
||||
"tree" : {
|
||||
"subscribe" : "구독",
|
||||
"import" : "임포트",
|
||||
"import" : "가져오기",
|
||||
"new_category" : "새로운 카테고리",
|
||||
"all" : "전체",
|
||||
"starred" : "스타"
|
||||
"starred" : "중요 표시됨"
|
||||
},
|
||||
"subscribe" : {
|
||||
"feed_url" : "피드 URL",
|
||||
"feed_name" : "피드 이름",
|
||||
"category" : "카테로기"
|
||||
"category" : "카테고리"
|
||||
},
|
||||
"import" : {
|
||||
"google_reader_prefix" : "당신의 Google Reader",
|
||||
@@ -31,23 +31,24 @@
|
||||
},
|
||||
"new_category" : {
|
||||
"name" : "이름",
|
||||
"parent" : "카테고리 주소로 가기"
|
||||
"parent" : "부모 카테고리"
|
||||
},
|
||||
"toolbar" : {
|
||||
"unread" : "안읽음",
|
||||
"unread" : "읽지 않음",
|
||||
"all" : "전체",
|
||||
"previous_entry" : "Previous entry ",
|
||||
"next_entry" : "Next entry ",
|
||||
"refresh" : "리프래쉬",
|
||||
"refresh_all" : "Force refresh all my feeds ",
|
||||
"sort_by_asc_desc" : "Sort by date asc/desc ",
|
||||
"titles_only" : "Titles only ",
|
||||
"expanded_view" : "Expanded view ",
|
||||
"mark_all_as_read" : "읽음표시",
|
||||
"mark_all_older_12_hours" : "Items older than 12 hours ",
|
||||
"mark_all_older_day" : "Items older than a day ",
|
||||
"mark_all_older_week" : "Items older than a week ",
|
||||
"mark_all_older_two_weeks" : "Items older than two weeks ",
|
||||
"previous_entry" : "이전 항목",
|
||||
"next_entry" : "다음 항목",
|
||||
"refresh" : "새로고침",
|
||||
"refresh_all" : "모든 피드를 강제로 새로고침",
|
||||
"sort_by_asc_desc" : "날짜별 오름차/내림차순 정렬",
|
||||
"sort_by_abc_zyx" : "Sort Alphabetically",
|
||||
"titles_only" : "제목만 표시하기",
|
||||
"expanded_view" : "Expanded View",
|
||||
"mark_all_as_read" : "읽음으로 표시",
|
||||
"mark_all_older_12_hours" : "12시간보다 오래된 항목",
|
||||
"mark_all_older_day" : "1일보다 오래된 항목",
|
||||
"mark_all_older_week" : "1주일보다 오래된 항목",
|
||||
"mark_all_older_two_weeks" : "2주일보다 오래된 항목",
|
||||
"settings" : "설정",
|
||||
"profile" : "프로필",
|
||||
"admin" : "괸리자",
|
||||
@@ -57,124 +58,124 @@
|
||||
},
|
||||
"view" : {
|
||||
"entry_source" : "from ",
|
||||
"entry_author" : "by ",
|
||||
"error_while_loading_feed" : "피드로딩중 에러",
|
||||
"keep_unread" : "안읽은것 저장",
|
||||
"no_unread_items" : " 읽지않은 항목이 없읍니다.",
|
||||
"mark_up_to_here" : "Mark as read up to here ",
|
||||
"search_for" : "searching for: ",
|
||||
"no_search_results" : "No match found for the requested keywords "
|
||||
"entry_author" : "by ",
|
||||
"error_while_loading_feed" : "피드 로딩중 에러",
|
||||
"keep_unread" : "항상 읽지 않음으로 표시",
|
||||
"no_unread_items" : " 읽지 않은 항목이 없습니다.",
|
||||
"mark_up_to_here" : "이 위로 읽음으로 표시",
|
||||
"search_for" : "검색: ",
|
||||
"no_search_results" : "검색 결과 없음"
|
||||
},
|
||||
"feedsearch" : {
|
||||
"hint" : "Type in a subscription... ",
|
||||
"help" : "Use the return key to select and arrow keys to navigate. ",
|
||||
"result_prefix" : "Your subscriptions: "
|
||||
"hint" : "구독 이름을 입력하세요",
|
||||
"help" : "화살표 키로 이동하고 엔터 키로 선택하세요.",
|
||||
"result_prefix" : "검색 결과:"
|
||||
},
|
||||
"settings" : {
|
||||
"general" : {
|
||||
"value" : "일반",
|
||||
"language" : "일반 언어",
|
||||
"language_contribute" : "번역 도움하기",
|
||||
"show_unread" : "안읽은 항목들이 있는 피드와 카테고리 보여주기",
|
||||
"social_buttons" : "소셜미디아 버튼들 보여주기",
|
||||
"scroll_marks" : "Expanded View에서 스크롤하면 항목들을 읽음으로 저장하기"
|
||||
"language" : "언어",
|
||||
"language_contribute" : "번역에 기여하기",
|
||||
"show_unread" : "안 읽은 항목들이 있는 피드와 카테고리 보여주기",
|
||||
"social_buttons" : "공유 버튼 표시하기",
|
||||
"scroll_marks" : "Expanded View에서 스크롤하면 항목들을 읽음으로 표시하기"
|
||||
},
|
||||
"appearance" : "Appearance ",
|
||||
"scroll_speed" : "Scrolling speed when navigating between entries (in milliseconds) ",
|
||||
"scroll_speed_help" : "set to 0 to disable ",
|
||||
"theme" : "Theme ",
|
||||
"submit_your_theme" : "Submit your theme ",
|
||||
"custom_css" : "커스톰 CSS"
|
||||
"appearance" : "외관",
|
||||
"scroll_speed" : "항목 사이를 이동할 때 스크롤 속도 (밀리초로 설정)",
|
||||
"scroll_speed_help" : "비활성화하려면 0으로 설정하세요",
|
||||
"theme" : "테마",
|
||||
"submit_your_theme" : "새 테마 업로드",
|
||||
"custom_css" : "커스텀 CSS"
|
||||
},
|
||||
"details" : {
|
||||
"feed_details" : "피드 세부",
|
||||
"url" : "유알엘",
|
||||
"website" : "Website ",
|
||||
"feed_details" : "피드 상세",
|
||||
"url" : "URL",
|
||||
"website" : "웹사이트",
|
||||
"name" : "이름",
|
||||
"category" : "카테고리",
|
||||
"position" : "Position ",
|
||||
"last_refresh" : "마지막 리프래쉬",
|
||||
"message" : "Last refresh message ",
|
||||
"next_refresh" : "Next refresh ",
|
||||
"queued_for_refresh" : "Queued for refresh ",
|
||||
"feed_url" : "피드 유알엘",
|
||||
"position" : "위치",
|
||||
"last_refresh" : "마지막 새로고침",
|
||||
"message" : "마지막 새로고침 메시지",
|
||||
"next_refresh" : "다음 새로고침",
|
||||
"queued_for_refresh" : "새로고침 대기중",
|
||||
"feed_url" : "피드 URL",
|
||||
"generate_api_key_first" : "당신의 프로필을 위해 API Key를 먼저 생성하세요.",
|
||||
"unsubscribe" : "주소 삭제",
|
||||
"unsubscribe_confirmation" : "Are you sure you want to unsubscribe from this feed? ",
|
||||
"delete_category_confirmation" : "Are you sure you want to delete this category? ",
|
||||
"category_details" : "카테고리 세부",
|
||||
"tag_details" : "Tag details ",
|
||||
"unsubscribe" : "구독 해제",
|
||||
"unsubscribe_confirmation" : "정말 이 피드를 구독 해제하시겠습니까?",
|
||||
"delete_category_confirmation" : "정말 이 카테고리를 삭제하시겠습니까?",
|
||||
"category_details" : "카테고리 상세",
|
||||
"tag_details" : "태그 상세",
|
||||
"parent_category" : "부모 카테고리"
|
||||
},
|
||||
"profile" : {
|
||||
"user_name" : "사용자 이름",
|
||||
"email" : "이메일",
|
||||
"change_password" : "비밀번호변경",
|
||||
"confirm_password" : "비밀번호확인",
|
||||
"minimum_6_chars" : "최소로 6자문자가 필요합니다.",
|
||||
"change_password" : "비밀번호 변경",
|
||||
"confirm_password" : "비밀번호 확인",
|
||||
"minimum_6_chars" : "최소 6개의 문자가 필요합니다.",
|
||||
"passwords_do_not_match" : "비밀번호가 일치하지 않습니다.",
|
||||
"api_key" : "API key",
|
||||
"api_key_not_generated" : "아직 API Key가 생성되지 않았습니다.",
|
||||
"generate_new_api_key" : "API Key 생성하기",
|
||||
"generate_new_api_key_info" : "비밀번호를 변경하면 새로운 API Key가 생성됩니다.",
|
||||
"opml_export" : "OPML export ",
|
||||
"delete_account" : "프로필삭제",
|
||||
"delete_account_confirmation" : "Delete your acount? There's no turning back! "
|
||||
"opml_export" : "OPML 내보내기",
|
||||
"delete_account" : "계정 삭제하기",
|
||||
"delete_account_confirmation" : "계정을 삭제하시겠습니까? 되돌릴 수 없어요!"
|
||||
},
|
||||
"about" : {
|
||||
"rest_api" : {
|
||||
"value" : "REST API",
|
||||
"line1" : "CommaFeed는 JAX-RS하고 AngularJS를 이용해서 만들었습니다. 그렇기 때문에 REST API를 사용할수있습니다.",
|
||||
"line1" : "CommaFeed는 JAX-RS와 AngularJS를 이용해 만들었습니다. 그렇기 때문에 REST API를 사용할수있습니다.",
|
||||
"link_to_documentation" : "문서 링크."
|
||||
},
|
||||
"keyboard_shortcuts" : "단축기",
|
||||
"version" : "CommaFeed version ",
|
||||
"line1_prefix" : "CommaFeed는 오픈 소스프로젝트입니다. 소스는",
|
||||
"keyboard_shortcuts" : "단축키",
|
||||
"version" : "CommaFeed 버전",
|
||||
"line1_prefix" : "CommaFeed는 오픈 소스 프로젝트입니다. 소스는",
|
||||
"line1_suffix" : "에 있습니다.",
|
||||
"line2_prefix" : "문제가 발생하는 경우",
|
||||
"line2_suffix" : " 프로젝트 문제페이지에 보고하십시요.",
|
||||
"line3" : "이 프로젝트를 좋아하시면 개발자를 지원하고 웹사이트 유지용비를 충당하는 데 도움이되는 기부금을 고려하시기 바랍니다.",
|
||||
"line4" : "For those of you who prefer bitcoin, here is the address ",
|
||||
"line2_suffix" : " 프로젝트 문제 페이지에 보고하십시오.",
|
||||
"line3" : "이 프로젝트를 좋아하시면 개발자를 지원하고 웹사이트 유지비용을 충당하는 데 도움이 되는 기부금을 고려하시기 바랍니다.",
|
||||
"line4" : "비트코인으로 기부하기",
|
||||
"goodies" : {
|
||||
"value" : "Goodies",
|
||||
"android_app" : "Android app ",
|
||||
"subscribe_url" : "Subscribe URL ",
|
||||
"chrome_extension" : "Chrome extension ",
|
||||
"firefox_extension" : "Firefox extension ",
|
||||
"opera_extension" : "Opera extension ",
|
||||
"subscribe_bookmarklet" : "Add subscription bookmarklet (click) ",
|
||||
"subscribe_bookmarklet_asc" : "Oldest first ",
|
||||
"subscribe_bookmarklet_desc" : "Newest first ",
|
||||
"next_unread_bookmarklet" : "Next unread item bookmarklet (drag to bookmark bar) "
|
||||
"android_app" : "안드로이드 앱",
|
||||
"subscribe_url" : "구독 URL",
|
||||
"chrome_extension" : "Chrome 확장 프로그램",
|
||||
"firefox_extension" : "Firefox 확장 기능",
|
||||
"opera_extension" : "Opera 확장 기능",
|
||||
"subscribe_bookmarklet" : "구독 북마크 추가 (클릭)",
|
||||
"subscribe_bookmarklet_asc" : "오래된 것 먼저",
|
||||
"subscribe_bookmarklet_desc" : "새로운 것 먼저",
|
||||
"next_unread_bookmarklet" : "안 읽은 항목 북마크 (북마크바에 끌기) "
|
||||
},
|
||||
"translation" : {
|
||||
"value" : "번역",
|
||||
"message" : "CommaFeed를 번역할려면 당신의 도움이 필요합니다.",
|
||||
"message" : "CommaFeed를 번역하는데 당신의 도움이 필요합니다.",
|
||||
"link" : "번역에 기여하기"
|
||||
},
|
||||
"announcements" : "공지",
|
||||
"shortcuts" : {
|
||||
"mouse_middleclick" : "마우시 미들클릭",
|
||||
"mouse_middleclick" : "마우스 미들클릭",
|
||||
"open_next_entry" : "다음 항목 열기",
|
||||
"open_previous_entry" : "이전 항목 열기",
|
||||
"spacebar" : "space/shift+space ",
|
||||
"move_page_down_up" : "moves the page down/up ",
|
||||
"focus_next_entry" : "set focus on next entry without opening it ",
|
||||
"focus_previous_entry" : "set focus on previous entry without opening it ",
|
||||
"open_next_feed" : "open next feed or category ",
|
||||
"open_previous_feed" : "open previous feed or category ",
|
||||
"move_page_down_up" : "페이지 아래/위로 이동 ",
|
||||
"focus_next_entry" : "열지 않고 다음 항목 보기",
|
||||
"focus_previous_entry" : "열지 않고 이전 항목 보기",
|
||||
"open_next_feed" : "다음 피드나 카테고리 열기",
|
||||
"open_previous_feed" : "이전 피드나 카테고리 열기",
|
||||
"open_close_current_entry" : "현재 항목 열기/닫기",
|
||||
"open_current_entry_in_new_window" : "새 창에서 현재 항목열기",
|
||||
"open_current_entry_in_new_window_background" : "open current entry in a new window in the background ",
|
||||
"star_unstar" : "현재 항목 스타/스타제거",
|
||||
"open_current_entry_in_new_window" : "새 창으로 현재 항목 열기",
|
||||
"open_current_entry_in_new_window_background" : "백그라운드에 새 창으로 현재 항목 열기",
|
||||
"star_unstar" : "현재 항목 중요 표시/중요 표시 제거",
|
||||
"mark_current_entry" : "현재 항목 읽음/안읽음 표시",
|
||||
"mark_all_as_read" : "모든 항목 읽음으로 표시",
|
||||
"open_in_new_tab_mark_as_read" : "읽음으로 표시하고 새로운 탭에서 열기",
|
||||
"fullscreen" : "toggle full screen mode ",
|
||||
"font_size" : "increase/decrease font size of the current entry ",
|
||||
"go_to_all" : "go to the All view ",
|
||||
"go_to_starred" : "go to the Starred view ",
|
||||
"feed_search" : "navigate to a subscription by entering the subscription name "
|
||||
"fullscreen" : "전체화면 켜기/끄기",
|
||||
"font_size" : "현재 항목의 글꼴 크기를 크게/작게",
|
||||
"go_to_all" : "모든 항목 보기",
|
||||
"go_to_starred" : "중요 표시한 항목 보기",
|
||||
"feed_search" : "구독 이름으로 구독 찾기"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"refresh" : "Refresh",
|
||||
"refresh_all" : "Force refresh all my feeds ",
|
||||
"sort_by_asc_desc" : "Aturkan mengikut tarikh (baru/lama)",
|
||||
"sort_by_abc_zyx" : "Sort Alphabetically",
|
||||
"titles_only" : "Tajuk sahaja",
|
||||
"expanded_view" : "Wide view",
|
||||
"mark_all_as_read" : "Tanda kesemuanya telah dibaca",
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"refresh" : "Oppdater",
|
||||
"refresh_all" : "Force refresh all my feeds ",
|
||||
"sort_by_asc_desc" : "Sorter etter dato ny/gammel",
|
||||
"sort_by_abc_zyx" : "Sort Alphabetically",
|
||||
"titles_only" : "Kun titler",
|
||||
"expanded_view" : "Utvidet visning",
|
||||
"mark_all_as_read" : "Merk alle som lest",
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"refresh" : "Vernieuwen",
|
||||
"refresh_all" : "Force refresh all my feeds ",
|
||||
"sort_by_asc_desc" : "Sorteer op datum opl/afl",
|
||||
"sort_by_abc_zyx" : "Sort Alphabetically",
|
||||
"titles_only" : "Alleen titels",
|
||||
"expanded_view" : "Uitgebreide weergave",
|
||||
"mark_all_as_read" : "Markeer alles als gelezen",
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"refresh" : "Oppdater",
|
||||
"refresh_all" : "Force refresh all my feeds ",
|
||||
"sort_by_asc_desc" : "Sorter etter dato ny/gamal",
|
||||
"sort_by_abc_zyx" : "Sort Alphabetically",
|
||||
"titles_only" : "Berre titlar",
|
||||
"expanded_view" : "Utvida visning",
|
||||
"mark_all_as_read" : "Merk alle som lesne",
|
||||
|
||||
@@ -39,12 +39,13 @@
|
||||
"previous_entry" : "Poprzedni element",
|
||||
"next_entry" : "Następny element",
|
||||
"refresh" : "Odswież",
|
||||
"refresh_all" : "Force refresh all my feeds ",
|
||||
"refresh_all" : "Odśwież teraz wszystkie kanały ",
|
||||
"sort_by_asc_desc" : "Sortuj od najnowszego/najstarszego",
|
||||
"sort_by_abc_zyx" : "Sortuj alfabetycznie",
|
||||
"titles_only" : "Widok listy",
|
||||
"expanded_view" : "Widok rozwinięty",
|
||||
"mark_all_as_read" : "Oznacz wszystko jako przeczytane",
|
||||
"mark_all_older_12_hours" : "Items older than 12 hours ",
|
||||
"mark_all_older_12_hours" : "Elementy starsze niż 12 godzin ",
|
||||
"mark_all_older_day" : "Elementy starsze niż dzień",
|
||||
"mark_all_older_week" : "Elementy starsze niż tydzień",
|
||||
"mark_all_older_two_weeks" : "Elementy starsze niż dwa tygodnie",
|
||||
@@ -61,9 +62,9 @@
|
||||
"error_while_loading_feed" : "Wystąpił błąd podczas ładowania tego kanału.",
|
||||
"keep_unread" : "Pozostaw nieprzeczytane",
|
||||
"no_unread_items" : " nie ma nieprzeczytanych elementów.",
|
||||
"mark_up_to_here" : "Mark as read up to here ",
|
||||
"search_for" : "searching for: ",
|
||||
"no_search_results" : "No match found for the requested keywords "
|
||||
"mark_up_to_here" : "Oznacz jako przeczytane do tego elementu ",
|
||||
"search_for" : "wyszukiwanie dla: ",
|
||||
"no_search_results" : "Nie znaleziono wyników dla wyszukiwanej frazy "
|
||||
},
|
||||
"feedsearch" : {
|
||||
"hint" : "Wpisz subskrybcję...",
|
||||
@@ -80,8 +81,8 @@
|
||||
"scroll_marks" : "W widoku rozwiniętym przewijanie oznacza elementy jako przeczytane"
|
||||
},
|
||||
"appearance" : "Wygląd",
|
||||
"scroll_speed" : "Scrolling speed when navigating between entries (in milliseconds) ",
|
||||
"scroll_speed_help" : "set to 0 to disable ",
|
||||
"scroll_speed" : "Prędkość przewijania podczas nawigowania pomiędzy wpisami (w milisekundach) ",
|
||||
"scroll_speed_help" : "ustaw na 0 by wyłączyć ",
|
||||
"theme" : "Motyw",
|
||||
"submit_your_theme" : "Wyślij swój motyw",
|
||||
"custom_css" : "Własny styl CSS"
|
||||
@@ -89,21 +90,23 @@
|
||||
"details" : {
|
||||
"feed_details" : "Szczegóły kanału",
|
||||
"url" : "URL",
|
||||
"website" : "Website ",
|
||||
"website" : "Strona internetowa",
|
||||
"name" : "Nazwa",
|
||||
"category" : "Kategoria",
|
||||
"position" : "Pozycja",
|
||||
"last_refresh" : "Ostatnio odświeżony",
|
||||
"message" : "Last refresh message ",
|
||||
"message" : "Ostatnia odpowiedź odświeżenia",
|
||||
"next_refresh" : "Następne odświeżenie",
|
||||
"queued_for_refresh" : "W kolejce do odświeżenia",
|
||||
"feed_url" : "URL kanału",
|
||||
"filtering_expression" : "Wyrażenie filtrujące",
|
||||
"filtering_expression_help" : "Ustaw puste, by wyłączyć. W przeciwnym razie wyrażenie zwracające 'true' lub 'false'. Dla 'false' nowe elementy w kanale będą autmatycznie \noznaczane jako przeczytane. Dostępne zmienne to: 'title', 'content', 'url' 'author' and 'categories'. Ich zawartość jest konwertowana na małe litery \npodczas porówynywania tekstu. Przykład: url.contains('youtube') albo (author eq 'athou' and title.contains('github') \nPełna dostępna składnia jest dostępna pod <a href='http://commons.apache.org/proper/commons-jexl/reference/syntax.html' target='_blank'>tym</a> adresem.",
|
||||
"generate_api_key_first" : "Najpierw wygeneruj klucz API w swoim profilu.",
|
||||
"unsubscribe" : "Cofnij subskrypcje",
|
||||
"unsubscribe_confirmation" : "Are you sure you want to unsubscribe from this feed? ",
|
||||
"delete_category_confirmation" : "Are you sure you want to delete this category? ",
|
||||
"unsubscribe_confirmation" : "Czy na pewno chcesz cofnąć sybskrypcję tego kanału? ",
|
||||
"delete_category_confirmation" : "Czy na pewno chcesz usunąć tą kategorię? ",
|
||||
"category_details" : "Szczegóły kategorii",
|
||||
"tag_details" : "Tag details ",
|
||||
"tag_details" : "Szczegóły tagu ",
|
||||
"parent_category" : "Kategoria nadrzędna"
|
||||
},
|
||||
"profile" : {
|
||||
@@ -119,7 +122,7 @@
|
||||
"generate_new_api_key_info" : "Zmiana hasła spowoduje wygenerowanie nowego klucza API",
|
||||
"opml_export" : "Eksportuj do pliku OPML",
|
||||
"delete_account" : "Usuń konto",
|
||||
"delete_account_confirmation" : "Delete your acount? There's no turning back! "
|
||||
"delete_account_confirmation" : "Na pewno usunąć to konto? Nie można tego cofnąć! "
|
||||
},
|
||||
"about" : {
|
||||
"rest_api" : {
|
||||
@@ -143,8 +146,8 @@
|
||||
"firefox_extension" : "Dodatek do Firefoxa",
|
||||
"opera_extension" : "Dodatek do Opery",
|
||||
"subscribe_bookmarklet" : "Dodaj subskrybcje jako skryptozakładkę (kliknij)",
|
||||
"subscribe_bookmarklet_asc" : "Oldest first ",
|
||||
"subscribe_bookmarklet_desc" : "Newest first ",
|
||||
"subscribe_bookmarklet_asc" : "Najpierw najstarsze ",
|
||||
"subscribe_bookmarklet_desc" : "Najpierw najnowsze ",
|
||||
"next_unread_bookmarklet" : "Następny nieprzeczytany element jako skryptozakładka (przeciągnij na pasek zakładek)"
|
||||
},
|
||||
"translation" : {
|
||||
@@ -172,9 +175,9 @@
|
||||
"open_in_new_tab_mark_as_read" : "otwórz w nowej zakładce i oznacz jako przeczytane",
|
||||
"fullscreen" : "przełącz tryb pełnoekranowy",
|
||||
"font_size" : "zmień wielkość czcionki",
|
||||
"go_to_all" : "go to the All view ",
|
||||
"go_to_starred" : "go to the Starred view ",
|
||||
"go_to_all" : "przejdź do widoku Wszystkich elementów ",
|
||||
"go_to_starred" : "przejdź do Elementów oznaczonych gwiazdką ",
|
||||
"feed_search" : "przejdź do subskrybcji wpisując jej nazwę"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"refresh" : "Atualizar",
|
||||
"refresh_all" : "Forçar atualização de todos os meus feeds",
|
||||
"sort_by_asc_desc" : "Ordenar por data cresc/decres",
|
||||
"sort_by_abc_zyx" : "Sort Alphabetically",
|
||||
"titles_only" : "Somente títulos",
|
||||
"expanded_view" : "Modo Expandido",
|
||||
"mark_all_as_read" : "Marcar tudo como lido",
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"refresh" : "Обновить",
|
||||
"refresh_all" : "Обновить все подписки вручную",
|
||||
"sort_by_asc_desc" : "Сначала новые/старые",
|
||||
"sort_by_abc_zyx" : "Sort Alphabetically",
|
||||
"titles_only" : "Только заголовки",
|
||||
"expanded_view" : "Развёрнутый вид",
|
||||
"mark_all_as_read" : "Отметить всё как прочитанное",
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"refresh" : "Obnoviť",
|
||||
"refresh_all" : "Vynútené obnovenie všetkých položiek",
|
||||
"sort_by_asc_desc" : "Zoradiť podľa najnovšieho/najstaršieho",
|
||||
"sort_by_abc_zyx" : "Sort Alphabetically",
|
||||
"titles_only" : "Náhľad titulkov",
|
||||
"expanded_view" : "Rozšírený náhľad",
|
||||
"mark_all_as_read" : "Označiť všetky ako prečítané",
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"refresh" : "Uppdatera",
|
||||
"refresh_all" : "Tvinga uppdatering av alla prenumerationer",
|
||||
"sort_by_asc_desc" : "Sortera efter datum stigande/fallande",
|
||||
"sort_by_abc_zyx" : "Sort Alphabetically",
|
||||
"titles_only" : "Endast titlar",
|
||||
"expanded_view" : "Expanderad vy",
|
||||
"mark_all_as_read" : "Markera alla som lästa",
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"link" : "Bağlantı",
|
||||
"bookmark" : "Yer imi",
|
||||
"close" : "Kapat",
|
||||
"tags" : "Tags "
|
||||
"tags" : "Etiketler "
|
||||
},
|
||||
"tree" : {
|
||||
"subscribe" : "Abone ol",
|
||||
@@ -24,7 +24,7 @@
|
||||
},
|
||||
"import" : {
|
||||
"google_reader_prefix" : "Aboneliklerinizi ",
|
||||
"google_reader_suffix" : " hesabınızdan aktarmama izin verin.",
|
||||
"google_reader_suffix" : "Hesabınızdan aktarmama izin verin.",
|
||||
"google_download" : "Veya, subscriptions.xml dosyanızı yükleyin.",
|
||||
"google_download_link" : "Buradan indirebilirsiniz.",
|
||||
"xml_file" : "OPML dosyası"
|
||||
@@ -39,15 +39,16 @@
|
||||
"previous_entry" : "Önceki ileti",
|
||||
"next_entry" : "Sonraki ileti",
|
||||
"refresh" : "Yenile",
|
||||
"refresh_all" : "Force refresh all my feeds ",
|
||||
"refresh_all" : "Tüm yayınları yenilemek için zorla",
|
||||
"sort_by_asc_desc" : "Tarihe göre sırala artan/azalan",
|
||||
"sort_by_abc_zyx" : "Sort Alphabetically",
|
||||
"titles_only" : "Sadece başlıklar",
|
||||
"expanded_view" : "Genişletilmiş görünüm",
|
||||
"mark_all_as_read" : "Tümünü okundu işaretle",
|
||||
"mark_all_older_12_hours" : "Items older than 12 hours ",
|
||||
"mark_all_older_day" : "Bir günden eski yazılar",
|
||||
"mark_all_older_week" : "Bir haftadan eski yazılar",
|
||||
"mark_all_older_two_weeks" : "İki haftadan eski yazılar",
|
||||
"mark_all_older_12_hours" : "12 saatten daha eski yayınlar ",
|
||||
"mark_all_older_day" : "Bir günden eski yayınlar",
|
||||
"mark_all_older_week" : "Bir haftadan eski yayınlar",
|
||||
"mark_all_older_two_weeks" : "İki haftadan eski yayınlar",
|
||||
"settings" : "Ayarlar",
|
||||
"profile" : "Profil",
|
||||
"admin" : "Yönetim",
|
||||
@@ -56,14 +57,14 @@
|
||||
"donate" : "Bağış"
|
||||
},
|
||||
"view" : {
|
||||
"entry_source" : "from ",
|
||||
"entry_author" : "by ",
|
||||
"entry_source" : "kaynak: ",
|
||||
"entry_author" : "yazar: ",
|
||||
"error_while_loading_feed" : "Bu aboneliği çekerken hata oluştu.",
|
||||
"keep_unread" : "Okunmadı olarak sakla",
|
||||
"no_unread_items" : "okunmamış ileti yok.",
|
||||
"mark_up_to_here" : "Mark as read up to here ",
|
||||
"no_unread_items" : "Okunmamış ileti yok.",
|
||||
"mark_up_to_here" : "Buraya kadar olan bütün yayınları okundu olarak işaretle!",
|
||||
"search_for" : "searching for: ",
|
||||
"no_search_results" : "No match found for the requested keywords "
|
||||
"no_search_results" : "İstenen anahtar kelimeler için eşleşme bulunamadı"
|
||||
},
|
||||
"feedsearch" : {
|
||||
"hint" : "Bir abonelik yazın...",
|
||||
@@ -80,8 +81,8 @@
|
||||
"scroll_marks" : "Genişletilmiş görünümde götüntülenen iletileri okunmuş işaretle"
|
||||
},
|
||||
"appearance" : "Görünüm",
|
||||
"scroll_speed" : "Scrolling speed when navigating between entries (in milliseconds) ",
|
||||
"scroll_speed_help" : "set to 0 to disable ",
|
||||
"scroll_speed" : "İçerikler arasında gezinirken kaydırma hızı (milisaniye cinsinden)",
|
||||
"scroll_speed_help" : "ayarı kapatmak için 0 yazınız",
|
||||
"theme" : "Tema",
|
||||
"submit_your_theme" : "Tema gönder",
|
||||
"custom_css" : "Kişiselleştirilmiş CSS"
|
||||
@@ -100,10 +101,10 @@
|
||||
"feed_url" : "Yayın URL'si",
|
||||
"generate_api_key_first" : "Öncelikle profilinizden bir API anahtarı oluşturun.",
|
||||
"unsubscribe" : "Aboneliği iptal et",
|
||||
"unsubscribe_confirmation" : "Are you sure you want to unsubscribe from this feed? ",
|
||||
"delete_category_confirmation" : "Are you sure you want to delete this category? ",
|
||||
"unsubscribe_confirmation" : "Bu yayından çıkmak istediğinizden emin misiniz? ",
|
||||
"delete_category_confirmation" : "Bu kategoriyi silmek istediğinizden emin misiniz? ",
|
||||
"category_details" : "Kategori detayları",
|
||||
"tag_details" : "Tag details ",
|
||||
"tag_details" : "Etiket detayları ",
|
||||
"parent_category" : "Üst kategori"
|
||||
},
|
||||
"profile" : {
|
||||
@@ -116,10 +117,10 @@
|
||||
"api_key" : "API anahtarı",
|
||||
"api_key_not_generated" : "Henüz oluşturulmadı",
|
||||
"generate_new_api_key" : "Yeni bir API anahtarı oluştur",
|
||||
"generate_new_api_key_info" : "Şifre değiştirmek API anahtarının da değiştirilmesine neden olcak.",
|
||||
"generate_new_api_key_info" : "Şifreyi değiştirmek API anahtarının da değiştirilmesine neden olcak.",
|
||||
"opml_export" : "OPML dışa aktar",
|
||||
"delete_account" : "Hesabı sil",
|
||||
"delete_account_confirmation" : "Delete your acount? There's no turning back! "
|
||||
"delete_account_confirmation" : "Hesabı silmek istediğinize emin misiniz? Bu işlemde geri dönüş yoktur! "
|
||||
},
|
||||
"about" : {
|
||||
"rest_api" : {
|
||||
@@ -128,24 +129,24 @@
|
||||
"link_to_documentation" : "Dökümantasyon için tıklayın."
|
||||
},
|
||||
"keyboard_shortcuts" : "Klavye kısayolları",
|
||||
"version" : "CommaFeed version ",
|
||||
"version" : "CommaFeed versiyon ",
|
||||
"line1_prefix" : "CommaFeed bir açık kaynak projedir. Kaynak dosyaları ",
|
||||
"line1_suffix" : " adresinde yayınlanır.",
|
||||
"line1_suffix" : "adresinde yayınlanır.",
|
||||
"line2_prefix" : "Lütfen, bir hata ile karşılaşırsanız bunu ",
|
||||
"line2_suffix" : " projesinde hatalar sayfasından rapor edin.",
|
||||
"line2_suffix" : "projesinde hatalar sayfasından rapor edin.",
|
||||
"line3" : "Eğer bu projeyi beğendiyseniz, lütfen bağış yaparak geliştiriciye bu sayfayı ayakta tutmasında yardımcı olun.",
|
||||
"line4" : "Bitcoin'i tercih edenler için adres ",
|
||||
"goodies" : {
|
||||
"value" : "Extralar",
|
||||
"android_app" : "Android app ",
|
||||
"value" : "Ekstralar",
|
||||
"android_app" : "Android eklentisi",
|
||||
"subscribe_url" : "Abonelik URL'si",
|
||||
"chrome_extension" : "Chrome eklentisi",
|
||||
"firefox_extension" : "Firefox eklentisi",
|
||||
"opera_extension" : "Opera eklentisi",
|
||||
"subscribe_bookmarklet" : "Bookmarklet'a abonelik ekle (tıklayın)",
|
||||
"subscribe_bookmarklet_asc" : "Oldest first ",
|
||||
"subscribe_bookmarklet_desc" : "Newest first ",
|
||||
"next_unread_bookmarklet" : "Bookmarklet'daki en son okunmamış ileti (Sık kullanılan çubuğuna sürükleyin)"
|
||||
"subscribe_bookmarklet" : "Yer imilerine abonelik ekle (tıklayın)",
|
||||
"subscribe_bookmarklet_asc" : "Eskiler önce",
|
||||
"subscribe_bookmarklet_desc" : "Yeniler önce ",
|
||||
"next_unread_bookmarklet" : "Yer imilerindeki en son okunmamış ileti (Sık kullanılan çubuğuna sürükleyin)"
|
||||
},
|
||||
"translation" : {
|
||||
"value" : "Çeviri",
|
||||
@@ -158,7 +159,7 @@
|
||||
"open_next_entry" : "sonraki öğeyi görüntüle",
|
||||
"open_previous_entry" : "önceki öğeyi görüntüle",
|
||||
"spacebar" : "space/shift+space ",
|
||||
"move_page_down_up" : "moves the page down/up ",
|
||||
"move_page_down_up" : "sayfayı aşağı/yukarı hareket ettir",
|
||||
"focus_next_entry" : "sonraki öğeyi görüntülemeden işaretle",
|
||||
"focus_previous_entry" : "önceki öğeyi görüntülemeden işaretle",
|
||||
"open_next_feed" : "sonraki aboneliği veya kategoriyi görüntüle",
|
||||
@@ -170,11 +171,11 @@
|
||||
"mark_current_entry" : "görüntülenen öğeyi okundu/okunmadı işaretle",
|
||||
"mark_all_as_read" : "tümünü okundu işaretle",
|
||||
"open_in_new_tab_mark_as_read" : "öğeyi yeni bir sekmede aç ve okundu işaretle",
|
||||
"fullscreen" : "toggle full screen mode ",
|
||||
"font_size" : "increase/decrease font size of the current entry ",
|
||||
"go_to_all" : "go to the All view ",
|
||||
"go_to_starred" : "go to the Starred view ",
|
||||
"fullscreen" : "tam ekran moduna geç ",
|
||||
"font_size" : "mevcut içerik için yazı boyunutunu arttır/azalt",
|
||||
"go_to_all" : "Tüm öğeleri görüntüle",
|
||||
"go_to_starred" : "yıldızlı öğerleri görüntüle",
|
||||
"feed_search" : "abonelik ismini yazarak aboneliğe git"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"refresh" : "刷新",
|
||||
"refresh_all" : "刷新所有订阅",
|
||||
"sort_by_asc_desc" : "按日期升序/降序排序",
|
||||
"sort_by_abc_zyx" : "Sort Alphabetically",
|
||||
"titles_only" : "仅显示标题",
|
||||
"expanded_view" : "显示内容",
|
||||
"mark_all_as_read" : "标记所有为已读",
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
<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" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<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" />
|
||||
@@ -12,7 +16,9 @@
|
||||
<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="icon" sizes="192x192" href="app-icon-192.png" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
|
||||
<meta name="theme-color" content="#F88A14" />
|
||||
<meta name="application-name" content="CommaFeed" />
|
||||
<meta name="msapplication-navbutton-color" content="#F88A14" />
|
||||
<meta name="msapplication-starturl" content="/" />
|
||||
@@ -39,7 +45,7 @@
|
||||
</div>
|
||||
|
||||
<!-- build:js js/app.js -->
|
||||
<script type="text/javascript" src="lib/lodash/dist/lodash.js"></script>
|
||||
<script type="text/javascript" src="lib/lodash/lodash.js"></script>
|
||||
<script type="text/javascript" src="lib/jquery/dist/jquery.js"></script>
|
||||
<script type="text/javascript" src="lib/jquery-ui/ui/jquery-ui.js"></script>
|
||||
<script type="text/javascript" src="lib/jquery-mousewheel/jquery.mousewheel.js"></script>
|
||||
@@ -62,8 +68,8 @@
|
||||
<script type="text/javascript" src="lib/angular-ui-select2/src/select2.js"></script>
|
||||
<script type="text/javascript" src="lib/select2/select2.js"></script>
|
||||
<script type="text/javascript" src="lib/mousetrap/mousetrap.js"></script>
|
||||
<script type="text/javascript" src="lib/momentjs/min/moment-with-langs.js"></script>
|
||||
<script type="text/javascript" src="lib/device.js/lib/device.js"></script>
|
||||
<script type="text/javascript" src="lib/momentjs/min/moment-with-locales.js"></script>
|
||||
<script type="text/javascript" src="lib/devicejs/lib/device.js"></script>
|
||||
|
||||
<script type="text/javascript" src="js/controllers.js"></script>
|
||||
<script type="text/javascript" src="js/directives.js"></script>
|
||||
|
||||
@@ -322,17 +322,21 @@ module.controller('FeedDetailsCtrl', ['$scope', '$state', '$stateParams', 'FeedS
|
||||
|
||||
$scope.save = function() {
|
||||
var sub = $scope.sub;
|
||||
$scope.error = null;
|
||||
FeedService.modify({
|
||||
id : sub.id,
|
||||
name : sub.name,
|
||||
position : sub.position,
|
||||
categoryId : sub.categoryId
|
||||
categoryId : sub.categoryId,
|
||||
filter : sub.filter
|
||||
}, function() {
|
||||
CategoryService.init();
|
||||
$state.transitionTo('feeds.view', {
|
||||
_id : 'all',
|
||||
_type : 'category'
|
||||
});
|
||||
}, function(e) {
|
||||
$scope.error = e.data;
|
||||
});
|
||||
};
|
||||
}]);
|
||||
@@ -489,6 +493,7 @@ module.controller('ToolbarCtrl', [
|
||||
type : $stateParams._type,
|
||||
id : $stateParams._id,
|
||||
olderThan : olderThan,
|
||||
keywords : $location.search().q,
|
||||
read : true
|
||||
});
|
||||
};
|
||||
@@ -526,7 +531,11 @@ module.controller('ToolbarCtrl', [
|
||||
|
||||
$scope.toggleOrder = function() {
|
||||
var settings = $scope.settingsService.settings;
|
||||
settings.readingOrder = settings.readingOrder == 'asc' ? 'desc' : 'asc';
|
||||
settings.readingOrder = settings.readingOrder == 'desc' ? 'asc' : 'desc';
|
||||
};
|
||||
$scope.toggleAbcOrder = function() {
|
||||
var settings = $scope.settingsService.settings;
|
||||
settings.readingOrder = settings.readingOrder == 'abc' ? 'zyx' : 'abc';
|
||||
};
|
||||
|
||||
$scope.toAdmin = function() {
|
||||
@@ -881,6 +890,7 @@ module.controller('FeedListCtrl', [
|
||||
service.mark({
|
||||
id : $scope.selectedId,
|
||||
olderThan : olderThan || $scope.timestamp,
|
||||
keywords : $location.search().q,
|
||||
read : true
|
||||
}, function() {
|
||||
CategoryService.refresh(function() {
|
||||
@@ -1365,7 +1375,7 @@ module.controller('SettingsCtrl', ['$scope', '$location', 'SettingsService', 'An
|
||||
|
||||
$scope.langs = LangService.langs;
|
||||
|
||||
$scope.themes = ['default', 'bootstrap', 'dark', 'ebraminio', 'MRACHINI', 'svetla', 'third'];
|
||||
$scope.themes = ['default', 'bootstrap', 'dark', 'ebraminio', 'MRACHINI', 'nightsky', 'svetla', 'third'];
|
||||
|
||||
$scope.settingsService = SettingsService;
|
||||
$scope.$watch('settingsService.settings', function(value) {
|
||||
|
||||
@@ -72,7 +72,7 @@ module.directive('tags', function() {
|
||||
tags : []
|
||||
};
|
||||
if (newValue) {
|
||||
data.tags = newValue.split(',');
|
||||
data.tags = newValue;
|
||||
}
|
||||
EntryService.tag(data);
|
||||
}
|
||||
@@ -308,7 +308,8 @@ module.directive('droppable', ['CategoryService', 'FeedService', function(Catego
|
||||
|
||||
var data = {
|
||||
id : source.id,
|
||||
name : source.name
|
||||
name : source.name,
|
||||
filter : source.filter
|
||||
};
|
||||
|
||||
if (source.children) {
|
||||
@@ -359,3 +360,14 @@ module.directive('metricGauge', function() {
|
||||
templateUrl : 'templates/_metrics.gauge.html'
|
||||
};
|
||||
});
|
||||
|
||||
module.directive('metricTimer', function() {
|
||||
return {
|
||||
scope : {
|
||||
metric : '=',
|
||||
label : '='
|
||||
},
|
||||
restrict : 'E',
|
||||
templateUrl : 'templates/_metrics.timer.html'
|
||||
};
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@ module.service('LangService', [function() {
|
||||
'gl': 'Galician',
|
||||
'glk': 'گیلکی',
|
||||
'hu': 'Magyar',
|
||||
'id': 'Indonesian',
|
||||
'ja': '日本語',
|
||||
'ko': '한국어',
|
||||
'nl': 'Nederlands',
|
||||
@@ -19,7 +20,7 @@ module.service('LangService', [function() {
|
||||
'nn': 'Norsk (nynorsk)',
|
||||
'pt': 'Português',
|
||||
'pl': 'Polski',
|
||||
'ru': 'русский',
|
||||
'ru': 'Русский',
|
||||
'fi': 'Suomi',
|
||||
'sv': 'Svenska',
|
||||
'zh': '简体中文',
|
||||
@@ -31,4 +32,4 @@ module.service('LangService', [function() {
|
||||
'cs': 'Čeština',
|
||||
'ms': 'Bahasa Malaysian'
|
||||
}
|
||||
}]);
|
||||
}]);
|
||||
|
||||
@@ -23,26 +23,23 @@ app.config([
|
||||
|
||||
$compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|javascript):/);
|
||||
var interceptor = ['$rootScope', '$q', '$injector', function(scope, $q, $injector) {
|
||||
|
||||
var success = function(response) {
|
||||
var f = {};
|
||||
|
||||
f.response = function(response) {
|
||||
return response;
|
||||
};
|
||||
var error = function(response) {
|
||||
|
||||
f.responseError = function(response) {
|
||||
var status = response.status;
|
||||
if (status == 401) {
|
||||
$injector.get('$state').transitionTo('welcome');
|
||||
}
|
||||
return $q.reject(response);
|
||||
};
|
||||
|
||||
var promise = function(promise) {
|
||||
return promise.then(success, error);
|
||||
};
|
||||
|
||||
return promise;
|
||||
return f;
|
||||
}];
|
||||
|
||||
$httpProvider.responseInterceptors.push(interceptor);
|
||||
$httpProvider.interceptors.push(interceptor);
|
||||
|
||||
$stateProvider.state('feeds', {
|
||||
'abstract' : true,
|
||||
|
||||
@@ -62,7 +62,7 @@ module.factory('SettingsService', ['$resource', '$translate', function($resource
|
||||
} else if (lang === 'ms') {
|
||||
lang = 'ms-my';
|
||||
}
|
||||
moment.lang(lang, {});
|
||||
moment.locale(lang, {});
|
||||
if (callback) {
|
||||
callback(data);
|
||||
}
|
||||
@@ -298,6 +298,7 @@ module.factory('EntryService', ['$resource', '$http', function($resource, $http)
|
||||
$http.get('rest/entry/tags').success(function(data) {
|
||||
res.tags = [];
|
||||
res.tags.push.apply(res.tags, data);
|
||||
res.tags.sort();
|
||||
});
|
||||
};
|
||||
var oldTag = res.tag;
|
||||
|
||||
31
src/main/app/manifest.json
Normal file
31
src/main/app/manifest.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "CommaFeed",
|
||||
"icons": [
|
||||
{
|
||||
"src": "app-icon-72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image/png",
|
||||
"density": "1.5"
|
||||
},
|
||||
{
|
||||
"src": "app-icon-114.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image/png",
|
||||
"density": "2.0"
|
||||
},
|
||||
{
|
||||
"src": "app-icon-144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image/png",
|
||||
"density": "3.0"
|
||||
},
|
||||
{
|
||||
"src": "app-icon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"density": "4.0"
|
||||
}
|
||||
],
|
||||
"start_url": "/",
|
||||
"display": "standalone"
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
@import "themes/bootstrap";
|
||||
@import "themes/ebraminio";
|
||||
@import "themes/MRACHINI";
|
||||
@import "themes/nightsky";
|
||||
@import "themes/svetla";
|
||||
@import "themes/dark";
|
||||
@import "themes/third";
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#loading-bar .bar {
|
||||
background: #2c3e50;
|
||||
height: 101px;
|
||||
top: -100px;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
#loading-bar .peg {
|
||||
@@ -9,4 +12,4 @@
|
||||
#loading-bar-spinner .spinner-icon {
|
||||
border-top-color: #2c3e50;
|
||||
border-left-color: #2c3e50;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,4 @@
|
||||
content: "\e018";
|
||||
font-family: "readabilicons";
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-size: 21px;
|
||||
top: 5px;
|
||||
position: relative;
|
||||
line-height: 0px;
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
a:focus {
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.container-full {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
@@ -43,6 +48,10 @@ label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.pre-wrap {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.form-horizontal .control-group {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
@@ -116,19 +125,23 @@ blockquote p {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.btn,.btn-large,.btn-small,.btn-mini {
|
||||
.form-group {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.btn, .btn-large, .btn-small, .btn-mini {
|
||||
border-top-left-radius: 2px;
|
||||
border-top-right-radius: 2px;
|
||||
border-bottom-left-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
}
|
||||
|
||||
.btn-group>.btn:first-child,.btn-group>.btn-large:first-child {
|
||||
.btn-group>.btn:first-child, .btn-group>.btn-large:first-child {
|
||||
border-top-left-radius: 2px;
|
||||
border-bottom-left-radius: 2px;
|
||||
}
|
||||
|
||||
.btn-group>.btn:last-child,.btn-group>.btn-large:last-child,.btn-group>.dropdown-toggle
|
||||
.btn-group>.btn:last-child, .btn-group>.btn-large:last-child, .btn-group>.dropdown-toggle
|
||||
{
|
||||
border-top-right-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
|
||||
126
src/main/app/sass/themes/_nightsky.scss
Normal file
126
src/main/app/sass/themes/_nightsky.scss
Normal file
@@ -0,0 +1,126 @@
|
||||
#theme-nightsky {
|
||||
a {
|
||||
color: #2A9FD6;
|
||||
}
|
||||
|
||||
.nav-pills > li.active > a, .nav-pills > li.active > a:hover, .nav-pills > li.active > a:focus {
|
||||
color: #FFF;
|
||||
background-color: #2A9FD6;
|
||||
}
|
||||
|
||||
body, .toolbar {
|
||||
color: #C6C6C6;
|
||||
background-color: #2F2F2F;
|
||||
}
|
||||
|
||||
.btn-default {
|
||||
color: #C6C6C6;
|
||||
background-color: #424242;
|
||||
border-color: #424242;
|
||||
}
|
||||
|
||||
.btn-default:hover, .btn-default:focus, .btn-default.focus, .btn-default:active,
|
||||
.btn-default.active, .open>.dropdown-toggle.btn-default {
|
||||
background-color: #282828;
|
||||
border-color: #232323;
|
||||
}
|
||||
|
||||
.css-treeview li .tree-item:hover {
|
||||
background-color: #282828;
|
||||
}
|
||||
|
||||
.css-treeview .unread-counter {
|
||||
color: #939393;
|
||||
}
|
||||
|
||||
.css-treeview .category-link, .css-treeview a {
|
||||
color: #939393;
|
||||
}
|
||||
|
||||
.css-treeview a .unread, .css-treeview .category-link .unread {
|
||||
color: #C6C6C6;
|
||||
}
|
||||
|
||||
.css-treeview .selected {
|
||||
color: #C00;
|
||||
}
|
||||
|
||||
.entrylist-header {
|
||||
border-bottom: 1px solid #282828;
|
||||
}
|
||||
|
||||
#feed-accordion .entry {
|
||||
border-bottom: 1px solid #282828;
|
||||
}
|
||||
|
||||
#feed-accordion .entry-body .entry-title {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#feed-accordion .entry-heading .entry-name {
|
||||
color: #939393;
|
||||
}
|
||||
|
||||
#feed-accordion .unread .entry-heading .entry-name {
|
||||
font-weight: normal;
|
||||
color: #C6C6C6;
|
||||
}
|
||||
|
||||
#feed-accordion .unread .entry-heading {
|
||||
background-color: #444;
|
||||
}
|
||||
|
||||
#feed-accordion .entry-heading {
|
||||
background-color: #2F2F2F;
|
||||
}
|
||||
|
||||
#feed-accordion .unread .entry-heading:hover {
|
||||
background-color: #2F2F2F;
|
||||
}
|
||||
|
||||
#feed-accordion .current.closed .entry-heading {
|
||||
background-color: #151515;
|
||||
}
|
||||
|
||||
#feed-accordion .entry-heading-link {
|
||||
color: #C6C6C6;
|
||||
}
|
||||
|
||||
#feed-accordion .entry-external-link {
|
||||
color: #C6C6C6;
|
||||
}
|
||||
|
||||
#feed-accordion .icon-star-empty {
|
||||
color: #C6C6C6;
|
||||
}
|
||||
|
||||
#feed-accordion .entry-heading .feed-name {
|
||||
color: #939393;
|
||||
}
|
||||
|
||||
#feed-accordion .entry-body-content {
|
||||
color: #C6C6C6;
|
||||
}
|
||||
|
||||
#feed-accordion .entry-buttons {
|
||||
background-color: #494949;
|
||||
border-top: 1px solid #282828;
|
||||
}
|
||||
|
||||
#feed-accordion .share-buttons a {
|
||||
color: #C6C6C6;
|
||||
}
|
||||
|
||||
#feed-accordion a.mark-up-to {
|
||||
color: #C6C6C6;
|
||||
}
|
||||
|
||||
#loading-bar .bar {
|
||||
background: #C6C6C6;
|
||||
}
|
||||
|
||||
#loading-bar .peg {
|
||||
box-shadow: 0 0 10px #C6C6C6, 0 0 5px #C6C6C6;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<button type="button" class="close" ng-click="close()">×</button>
|
||||
<h4>
|
||||
<input ng-model="filter" class="filter-input"
|
||||
ui-keydown="{'up': 'focusPrevious($event)', 'down': 'focusNext($event)', 'enter': 'openFocused()' }" placeholder="'feedsearch.hint' | translate"
|
||||
ui-keydown="{'up': 'focusPrevious($event)', 'down': 'focusNext($event)', 'enter': 'openFocused()' }" placeholder="{{'feedsearch.hint' | translate}}"
|
||||
focus="feedSearchModal">
|
||||
</h4>
|
||||
<small>{{ 'feedsearch.help' | translate }}</small>
|
||||
|
||||
@@ -4,14 +4,8 @@
|
||||
<dt>Mean</dt>
|
||||
<dd>{{metric.meanRate | number:2}}</dd>
|
||||
|
||||
<dt>1 min</dt>
|
||||
<dd>{{metric.oneMinuteRate | number:2}}</dd>
|
||||
|
||||
<dt>5 min</dt>
|
||||
<dd>{{metric.fiveMinuteRate | number:2}}</dd>
|
||||
|
||||
<dt>15 min</dt>
|
||||
<dd>{{metric.fifteenMinuteRate | number:2}}</dd>
|
||||
<dt>1/5/15 min</dt>
|
||||
<dd>{{metric.oneMinuteRate | number:2}} {{metric.fiveMinuteRate | number:2}} {{metric.fifteenMinuteRate | number:2}}</dd>
|
||||
|
||||
<dt>Total</dt>
|
||||
<dd>{{metric.count}}</dd>
|
||||
|
||||
17
src/main/app/templates/_metrics.timer.html
Normal file
17
src/main/app/templates/_metrics.timer.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<div>
|
||||
<span>{{label}}</span>
|
||||
<dl class="dl-horizontal">
|
||||
<dt>Mean</dt>
|
||||
<dd>{{metric.meanRate | number:2}}</dd>
|
||||
|
||||
<dt>1/5/15 min</dt>
|
||||
<dd>{{metric.oneMinuteRate | number:2}} {{metric.fiveMinuteRate | number:2}} {{metric.fifteenMinuteRate | number:2}}</dd>
|
||||
|
||||
<dt>Total</dt>
|
||||
<dd>{{metric.count}}</dd>
|
||||
|
||||
<dt>min/max/mean (ms)</dt>
|
||||
<dd>{{metric.snapshot.min/1000000 | number:0}} {{metric.snapshot.max/1000000 | number:0}} {{metric.snapshot.mean/1000000 | number:0}}</dd>
|
||||
|
||||
</dl>
|
||||
</div>
|
||||
@@ -1,12 +1,12 @@
|
||||
<span>
|
||||
<a ng-click="edit_mode=!edit_mode" class="nolink pointer">
|
||||
<span ng-click="edit_mode=!edit_mode" class="nolink pointer">
|
||||
<i class="icon-tags"></i>
|
||||
{{ 'global.tags' | translate }}
|
||||
</a>
|
||||
</span>
|
||||
<span ng-if="!edit_mode">
|
||||
<span class="label label-info" ng-repeat="tag in entry.tags">{{tag}}</span>
|
||||
</span>
|
||||
<span ng-if="edit_mode">
|
||||
<input type="text" ui-select2="select2Options" ng-model="entry.tags" class="tag-input" autofocus />
|
||||
<input type="hidden" ui-select2="select2Options" ng-model="entry.tags" class="tag-input" autofocus />
|
||||
</span>
|
||||
</span>
|
||||
@@ -6,14 +6,14 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="btn-group">
|
||||
<div class="btn-group" id="toolbar-nav">
|
||||
<a type="button" class="btn btn-default" ng-click="previousEntry()" title="{{ 'toolbar.previous_entry' | translate }}">
|
||||
<i class="icon-chevron-up"></i>
|
||||
</a>
|
||||
<a type="button" class="btn btn-default" ng-click="nextEntry()" title="{{ 'toolbar.next_entry' | translate }}">
|
||||
<i class="icon-chevron-down"></i>
|
||||
</a>
|
||||
<div class="btn-group">
|
||||
<div class="btn-group" id="toolbar-refresh">
|
||||
<a type="button" class="btn btn-default" ng-click="refresh()" title="{{ 'toolbar.refresh' | translate }}">
|
||||
<i class="icon-refresh"></i>
|
||||
</a>
|
||||
@@ -28,7 +28,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-group">
|
||||
<div class="btn-group" id="toolbar-mark-read">
|
||||
<a type="button" class="btn btn-default" ng-click="markAllAsRead()" title="{{ 'toolbar.mark_all_as_read' | translate }}">
|
||||
<i class="icon-ok"></i>
|
||||
</a>
|
||||
@@ -57,34 +57,38 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="actions btn-group">
|
||||
<div class="actions btn-group" id="toolbar-read-mode">
|
||||
<div ng-if="!MobileService.mobile || MobileService.rightMenu">
|
||||
<div class="btn-group read-mode">
|
||||
<button type="button" class="btn btn-default" ng-click="settingsService.settings.readingMode = 'unread'"
|
||||
ng-class="{'active': settingsService.settings.readingMode == 'unread'}">{{ 'toolbar.unread' | translate }}</button>
|
||||
<button type="button" class="btn btn-default" ng-click="settingsService.settings.readingMode = 'all'"
|
||||
ng-class="{'active': settingsService.settings.readingMode == 'all'}">{{ 'toolbar.all' | translate }}</button>
|
||||
<button type="button" class="btn btn-default" ng-click="settingsService.settings.readingMode = 'unread'" ng-class="{'active': settingsService.settings.readingMode == 'unread'}">{{
|
||||
'toolbar.unread' | translate }}</button>
|
||||
<button type="button" class="btn btn-default" ng-click="settingsService.settings.readingMode = 'all'" ng-class="{'active': settingsService.settings.readingMode == 'all'}">{{
|
||||
'toolbar.all' | translate }}</button>
|
||||
</div>
|
||||
|
||||
<div class="btn-group">
|
||||
<a type="button" class="btn btn-default" ng-click="toggleOrder()" title="{{ 'toolbar.sort_by_asc_desc' | translate }}">
|
||||
<i
|
||||
ng-class="{'icon-arrow-up' : settingsService.settings.readingOrder == 'asc', 'icon-arrow-down': settingsService.settings.readingOrder == 'desc'}"></i>
|
||||
<div class="btn-group" id="toolbar-read-order">
|
||||
<a type="button" class="btn btn-default" ng-click="toggleOrder()" title="{{ 'toolbar.sort_by_asc_desc' | translate }}"
|
||||
ng-class="{'active' : settingsService.settings.readingOrder == 'asc' || settingsService.settings.readingOrder == 'desc'}">
|
||||
<i ng-class="{'icon-arrow-up' : settingsService.settings.readingOrder == 'asc', 'icon-arrow-down': settingsService.settings.readingOrder != 'asc'}"></i>
|
||||
</a>
|
||||
<button type="button" class="btn btn-default" ng-click="toggleAbcOrder()" title="{{ 'toolbar.sort_by_abc_zyx' | translate }}"
|
||||
ng-class="{'active' : settingsService.settings.readingOrder == 'abc' || settingsService.settings.readingOrder == 'zyx'}">
|
||||
<i ng-class="{'icon-sort-by-alphabet' : settingsService.settings.readingOrder != 'zyx', 'icon-sort-by-alphabet-alt': settingsService.settings.readingOrder == 'zyx'}"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="btn-group">
|
||||
<a type="button" class="btn btn-default" ng-click="settingsService.settings.viewMode = 'title'"
|
||||
ng-class="{'active': settingsService.settings.viewMode == 'title'}" title="{{ 'toolbar.titles_only' | translate }}">
|
||||
<div class="btn-group" id="toolbar-read-view-settings">
|
||||
<a type="button" class="btn btn-default" ng-click="settingsService.settings.viewMode = 'title'" ng-class="{'active': settingsService.settings.viewMode == 'title'}"
|
||||
title="{{ 'toolbar.titles_only' | translate }}">
|
||||
<i class="icon-list"></i>
|
||||
</a>
|
||||
<a type="button" class="btn btn-default" ng-click="settingsService.settings.viewMode = 'expanded'"
|
||||
ng-class="{'active': settingsService.settings.viewMode == 'expanded'}" title="{{ 'toolbar.expanded_view' | translate }}">
|
||||
<a type="button" class="btn btn-default" ng-click="settingsService.settings.viewMode = 'expanded'" ng-class="{'active': settingsService.settings.viewMode == 'expanded'}"
|
||||
title="{{ 'toolbar.expanded_view' | translate }}">
|
||||
<i class="icon-th-list"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="btn-group">
|
||||
<div class="btn-group" id="toolbar-settings">
|
||||
<a class="btn btn-default" ng-click="toSettings()" title="{{ 'toolbar.settings' | translate }}">
|
||||
<i class="icon-cog"></i>
|
||||
</a>
|
||||
@@ -124,13 +128,13 @@
|
||||
</form>
|
||||
</div>
|
||||
<div class="btn-group donate">
|
||||
<a class="btn btn-success" type="button" ng-click="toHelp()" title="{{ 'toolbar.about' | translate }} / {{ 'toolbar.donate' | translate }}">
|
||||
<button class="btn btn-success" type="button" ng-click="toHelp()" title="{{ 'toolbar.about' | translate }} / {{ 'toolbar.donate' | translate }}">
|
||||
<i class="icon-info-sign"></i>
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<span ng-if="!MobileService.mobile" ng-bind-html="ServerService.announcement | trustHtml"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,21 +1,28 @@
|
||||
<div>
|
||||
<metric-meter metric="metrics.meters['com.commafeed.backend.feed.FeedQueues.refill']" label="'Refresh queue refill rate (/sec)'"></metric-meter>
|
||||
<metric-meter metric="metrics.meters['com.commafeed.backend.feed.FeedRefreshTaskGiver.feedRefreshed']" label="'Feed refreshed (/sec)'"></metric-meter>
|
||||
<metric-meter metric="metrics.meters['com.commafeed.backend.feed.FeedRefreshUpdater.feedUpdated']" label="'Feed updated (/sec)'"></metric-meter>
|
||||
<metric-meter metric="metrics.meters['com.commafeed.backend.feed.FeedRefreshUpdater.entryCacheHit']" label="'Entry cache hit (/sec)'"></metric-meter>
|
||||
<metric-meter metric="metrics.meters['com.commafeed.backend.feed.FeedRefreshUpdater.entryCacheMiss']" label="'Entry cache miss (/sec)'"></metric-meter>
|
||||
<div class="col-md-6">
|
||||
<metric-meter metric="metrics.meters['com.commafeed.backend.feed.FeedQueues.refill']" label="'Refresh queue refill rate (/sec)'"></metric-meter>
|
||||
<metric-meter metric="metrics.meters['com.commafeed.backend.feed.FeedRefreshTaskGiver.feedRefreshed']" label="'Feed refreshed (/sec)'"></metric-meter>
|
||||
<metric-meter metric="metrics.meters['com.commafeed.backend.feed.FeedRefreshUpdater.feedUpdated']" label="'Feed updated (/sec)'"></metric-meter>
|
||||
<metric-meter metric="metrics.meters['com.commafeed.backend.feed.FeedRefreshUpdater.entryCacheHit']" label="'Entry cache hit (/sec)'"></metric-meter>
|
||||
<metric-meter metric="metrics.meters['com.commafeed.backend.feed.FeedRefreshUpdater.entryCacheMiss']" label="'Entry cache miss (/sec)'"></metric-meter>
|
||||
|
||||
<metric-gauge metric="metrics.gauges['com.commafeed.backend.feed.FeedRefreshExecutor.feed-refresh-updater.active']"
|
||||
label="'Feed Updater active'"></metric-gauge>
|
||||
<metric-gauge metric="metrics.gauges['com.commafeed.backend.feed.FeedRefreshExecutor.feed-refresh-updater.pending']"
|
||||
label="'Feed Updater queued'"></metric-gauge>
|
||||
|
||||
<metric-gauge metric="metrics.gauges['com.commafeed.backend.feed.FeedRefreshExecutor.feed-refresh-updater.active']"
|
||||
label="'Feed Updater active'"></metric-gauge>
|
||||
<metric-gauge metric="metrics.gauges['com.commafeed.backend.feed.FeedRefreshExecutor.feed-refresh-updater.pending']"
|
||||
label="'Feed Updater queued'"></metric-gauge>
|
||||
<metric-gauge metric="metrics.gauges['com.commafeed.backend.feed.FeedRefreshExecutor.feed-refresh-worker.active']"
|
||||
label="'Feed Worker active'"></metric-gauge>
|
||||
<metric-gauge metric="metrics.gauges['com.commafeed.backend.feed.FeedRefreshExecutor.feed-refresh-worker.pending']"
|
||||
label="'Feed Worker queued'"></metric-gauge>
|
||||
|
||||
<metric-gauge metric="metrics.gauges['com.commafeed.backend.feed.FeedRefreshExecutor.feed-refresh-worker.active']"
|
||||
label="'Feed Worker active'"></metric-gauge>
|
||||
<metric-gauge metric="metrics.gauges['com.commafeed.backend.feed.FeedRefreshExecutor.feed-refresh-worker.pending']"
|
||||
label="'Feed Worker queued'"></metric-gauge>
|
||||
|
||||
<metric-gauge metric="metrics.gauges['com.commafeed.backend.feed.FeedQueues.addQueue']" label="'Task Giver Add Queue'"></metric-gauge>
|
||||
<metric-gauge metric="metrics.gauges['com.commafeed.backend.feed.FeedQueues.takeQueue']" label="'Task Giver Take Queue'"></metric-gauge>
|
||||
<metric-gauge metric="metrics.gauges['com.commafeed.backend.feed.FeedQueues.giveBackQueue']" label="'Task Giver Give Back Queue'"></metric-gauge>
|
||||
<metric-gauge metric="metrics.gauges['com.commafeed.backend.feed.FeedQueues.addQueue']" label="'Task Giver Add Queue'"></metric-gauge>
|
||||
<metric-gauge metric="metrics.gauges['com.commafeed.backend.feed.FeedQueues.takeQueue']" label="'Task Giver Take Queue'"></metric-gauge>
|
||||
<metric-gauge metric="metrics.gauges['com.commafeed.backend.feed.FeedQueues.giveBackQueue']" label="'Task Giver Give Back Queue'"></metric-gauge>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div ng-repeat="(name, timer) in metrics.timers">
|
||||
<metric-timer metric="timer" label="name"></metric-gauge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">{{ 'details.feed_url' | translate }}</label>
|
||||
<div class="col-sm-10 checkbox">
|
||||
<div class="col-sm-10 form-control-static">
|
||||
<a ng-show="user.apiKey" href="{{'rest/category/entriesAsFeed?id=' + category.id + '&apiKey=' + user.apiKey}}" target="_blank">{{ 'global.link' | translate }}</a>
|
||||
<span ng-show="!user.apiKey">{{ 'details.generate_api_key_first' | translate }}</span>
|
||||
</div>
|
||||
|
||||
@@ -3,15 +3,16 @@
|
||||
<h3>{{ 'details.feed_details' | translate }}</h3>
|
||||
</div>
|
||||
<form name="form" class="form-horizontal" ng-submit="save()">
|
||||
<div class="alert alert-danger" ng-if="error">{{ error }}</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">{{ 'details.url' | translate }}</label>
|
||||
<div class="col-sm-10 checkbox">
|
||||
<div class="col-sm-10 form-control-static">
|
||||
<a href="{{sub.feedUrl}}" target="_blank">{{sub.feedUrl}}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">{{ 'details.website' | translate }}</label>
|
||||
<div class="col-sm-10 checkbox">
|
||||
<div class="col-sm-10 form-control-static">
|
||||
<a href="{{sub.feedLink}}" target="_blank">{{sub.feedLink}}</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -49,26 +50,34 @@
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">{{ 'details.next_refresh' | translate }}</label>
|
||||
<div class="col-sm-10 checkbox">
|
||||
<div class="col-sm-10 form-control-static">
|
||||
<span>{{sub.nextRefresh|entryDate:('details.queued_for_refresh' | translate) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">{{ 'details.message' | translate }}</label>
|
||||
<div class="col-sm-10 checkbox">
|
||||
<div class="col-sm-10 form-control-static">
|
||||
<span>{{sub.message}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">{{ 'details.feed_url' | translate }}</label>
|
||||
<div class="col-sm-10 checkbox">
|
||||
<div class="col-sm-10 form-control-static">
|
||||
<a ng-show="user.apiKey" href="{{'rest/feed/entriesAsFeed?id=' + sub.id + '&apiKey=' + user.apiKey}}" target="_blank">{{ 'global.link' | translate }}</a>
|
||||
<span ng-show="!user.apiKey">{{ 'details.generate_api_key_first' | translate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">{{ 'details.filtering_expression' | translate }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" name="filter" ng-model="sub.filter" class="form-control"></input>
|
||||
<p class="help-block pre-wrap" ng-bind-html="'details.filtering_expression_help' | translate"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
<button type="submit" class="btn btn-primary">{{ 'global.save' | translate }}</button>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<span ng-switch-when="starred">{{ 'tree.starred' | translate }}</span>
|
||||
<span ng-switch-default>
|
||||
<span ng-hide="feedLink">{{name}}</span>
|
||||
<a ng-show="feedLink" href="{{feedLink}}" target="_blank">{{name}}</a>
|
||||
<a ng-show="feedLink" href="{{feedLink}}" target="_blank" rel="noreferrer">{{name}}</a>
|
||||
</span>
|
||||
</span>
|
||||
<span ng-show="name"> »</span>
|
||||
@@ -20,7 +20,7 @@
|
||||
<div ng-repeat="entry in entries" class="entry entry-font-size-{{font_size}}" id="entry_{{entry.id}}"
|
||||
ng-class="{unread: entry.read == false, current: current==entry, open: isOpen, closed: !isOpen }">
|
||||
<div class="entry-heading" ng-swipe-right="mark(entry, !entry.read)">
|
||||
<a href="{{entry.url}}" target="_blank" class="entry-heading-link" ng-click="noop($event)" ng-mouseup="entryClicked(entry, $event)">
|
||||
<a href="{{entry.url}}" target="_blank" rel="noreferrer" class="entry-heading-link" ng-click="noop($event)" ng-mouseup="entryClicked(entry, $event)">
|
||||
<span class="feed-name">
|
||||
<span class="star" ng-mouseup="star(entry, !entry.starred, $event)">
|
||||
<i ng-class="{'icon-star icon-star-yellow': entry.starred, 'icon-star-empty': !entry.starred}" class="pointer"></i>
|
||||
@@ -31,7 +31,7 @@
|
||||
<span class="entry-date">{{entry.date | entryDate}}</span>
|
||||
<span class="entry-name" ng-class="{shrink: true, rtl: entry.rtl}" ng-bind-html="entry.title | highlight:keywords | trustHtml"></span>
|
||||
</a>
|
||||
<a href="{{entry.url}}" target="_blank" class="entry-external-link" ng-click="mark(entry, true)">
|
||||
<a href="{{entry.url}}" target="_blank" rel="noreferrer" class="entry-external-link" ng-click="mark(entry, true)">
|
||||
<i class="icon-external-link"></i>
|
||||
</a>
|
||||
</div>
|
||||
@@ -39,7 +39,7 @@
|
||||
ng-mouseup="bodyClicked(entry, $event)" ng-class="{rtl: entry.rtl}">
|
||||
<div class="entry-header">
|
||||
<div class="entry-title">
|
||||
<a href="{{entry.url}}" target="_blank" ng-bind-html="entry.title | highlight:keywords | trustHtml"></a>
|
||||
<a href="{{entry.url}}" target="_blank" rel="noreferrer" ng-bind-html="entry.title | highlight:keywords | trustHtml"></a>
|
||||
<div class="entry-subtitle">
|
||||
<span class="entry-source" ng-if="selectedType == 'category'">
|
||||
<span class="entry-source-prefix">{{ 'view.entry_source' | translate }}</span>
|
||||
@@ -51,6 +51,9 @@
|
||||
<span class="entry-author-prefix">{{ 'view.entry_author' | translate }}</span>
|
||||
<span class="entry-author-name">{{entry.author}}</span>
|
||||
</span>
|
||||
<span class="entry-categories" ng-if="entry.categories">
|
||||
<span class="entry-categories-name">({{entry.categories}})</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -58,8 +61,7 @@
|
||||
<div class="entry-body-content">
|
||||
<div ng-if="!MobileService.mobile" ng-bind-html="entry.content | iframeHttpsRewrite| highlight:keywords | trustHtml"></div>
|
||||
<div ng-if="MobileService.mobile" ng-bind-html="entry.content | iframeHttpsRewrite| highlight:keywords | appendImageTitles | trustHtml"></div>
|
||||
|
||||
<div class="entry-enclosure" ng-if="entry.enclosureType">
|
||||
<div class="entry-enclosure" ng-if="entry.enclosureType && (entry.enclosureUrl && entry.content && entry.content.indexOf(entry.enclosureUrl) == -1)">
|
||||
<video controls ng-if="entry.enclosureType && entry.enclosureType.indexOf('video') == 0">
|
||||
<source ng-src="{{entry.enclosureUrl | trustUrl}}" type="{{entry.enclosureType}}" />
|
||||
</video>
|
||||
@@ -91,7 +93,7 @@
|
||||
title="Gmail" popup ng-if="settingsService.settings.gmail">
|
||||
<i class="icon-gmail"></i>
|
||||
</a>
|
||||
<a href="http://www.facebook.com/sharer.php?u=={{entry.url|escape}}" title="Facebook" popup ng-if="settingsService.settings.facebook">
|
||||
<a href="http://www.facebook.com/sharer.php?u={{entry.url|escape}}" title="Facebook" popup ng-if="settingsService.settings.facebook">
|
||||
<i class="icon-facebook"></i>
|
||||
</a>
|
||||
<a href="http://twitter.com/share?text={{entry.title|escape}}&url={{entry.url|escape}}" title="Twitter" popup
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
package com.commafeed;
|
||||
|
||||
import io.dropwizard.Application;
|
||||
import io.dropwizard.assets.AssetsBundle;
|
||||
import io.dropwizard.db.DataSourceFactory;
|
||||
import io.dropwizard.hibernate.HibernateBundle;
|
||||
import io.dropwizard.migrations.MigrationsBundle;
|
||||
import io.dropwizard.servlets.CacheBustingFilter;
|
||||
import io.dropwizard.setup.Bootstrap;
|
||||
import io.dropwizard.setup.Environment;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
import javax.servlet.DispatcherType;
|
||||
@@ -22,6 +14,7 @@ import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.eclipse.jetty.server.session.SessionHandler;
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
|
||||
import com.commafeed.backend.feed.FeedRefreshTaskGiver;
|
||||
import com.commafeed.backend.feed.FeedRefreshUpdater;
|
||||
@@ -39,10 +32,8 @@ import com.commafeed.backend.model.UserRole;
|
||||
import com.commafeed.backend.model.UserSettings;
|
||||
import com.commafeed.backend.service.StartupService;
|
||||
import com.commafeed.backend.service.UserService;
|
||||
import com.commafeed.backend.task.OldStatusesCleanupTask;
|
||||
import com.commafeed.backend.task.OrphansCleanupTask;
|
||||
import com.commafeed.frontend.auth.SecurityCheckProvider;
|
||||
import com.commafeed.frontend.auth.SecurityCheckProvider.SecurityCheckUserServiceProvider;
|
||||
import com.commafeed.backend.task.ScheduledTask;
|
||||
import com.commafeed.frontend.auth.SecurityCheckFactoryProvider;
|
||||
import com.commafeed.frontend.resource.AdminREST;
|
||||
import com.commafeed.frontend.resource.CategoryREST;
|
||||
import com.commafeed.frontend.resource.EntryREST;
|
||||
@@ -54,19 +45,22 @@ import com.commafeed.frontend.servlet.AnalyticsServlet;
|
||||
import com.commafeed.frontend.servlet.CustomCssServlet;
|
||||
import com.commafeed.frontend.servlet.LogoutServlet;
|
||||
import com.commafeed.frontend.servlet.NextUnreadServlet;
|
||||
import com.commafeed.frontend.session.SessionHelperProvider;
|
||||
import com.commafeed.frontend.session.SessionHelperFactoryProvider;
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import com.sun.jersey.api.core.ResourceConfig;
|
||||
import com.wordnik.swagger.config.ConfigFactory;
|
||||
import com.wordnik.swagger.config.ScannerFactory;
|
||||
import com.wordnik.swagger.config.SwaggerConfig;
|
||||
import com.wordnik.swagger.jaxrs.config.DefaultJaxrsScanner;
|
||||
import com.wordnik.swagger.jaxrs.listing.ApiDeclarationProvider;
|
||||
import com.wordnik.swagger.jaxrs.listing.ApiListingResourceJSON;
|
||||
import com.wordnik.swagger.jaxrs.listing.ResourceListingProvider;
|
||||
import com.wordnik.swagger.jaxrs.reader.DefaultJaxrsApiReader;
|
||||
import com.wordnik.swagger.reader.ClassReaders;
|
||||
import com.google.inject.Key;
|
||||
import com.google.inject.TypeLiteral;
|
||||
|
||||
import io.dropwizard.Application;
|
||||
import io.dropwizard.assets.AssetsBundle;
|
||||
import io.dropwizard.db.DataSourceFactory;
|
||||
import io.dropwizard.forms.MultiPartBundle;
|
||||
import io.dropwizard.hibernate.HibernateBundle;
|
||||
import io.dropwizard.migrations.MigrationsBundle;
|
||||
import io.dropwizard.server.DefaultServerFactory;
|
||||
import io.dropwizard.servlets.CacheBustingFilter;
|
||||
import io.dropwizard.setup.Bootstrap;
|
||||
import io.dropwizard.setup.Environment;
|
||||
|
||||
public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
|
||||
|
||||
@@ -89,36 +83,44 @@ public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
|
||||
FeedSubscription.class, User.class, UserRole.class, UserSettings.class) {
|
||||
@Override
|
||||
public DataSourceFactory getDataSourceFactory(CommaFeedConfiguration configuration) {
|
||||
return configuration.getDatabase();
|
||||
DataSourceFactory factory = configuration.getDataSourceFactory();
|
||||
|
||||
// keep using old id generator for backward compatibility
|
||||
factory.getProperties().put(AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, "false");
|
||||
|
||||
factory.getProperties().put(AvailableSettings.STATEMENT_BATCH_SIZE, "50");
|
||||
factory.getProperties().put(AvailableSettings.BATCH_VERSIONED_DATA, "true");
|
||||
return factory;
|
||||
}
|
||||
});
|
||||
|
||||
bootstrap.addBundle(new MigrationsBundle<CommaFeedConfiguration>() {
|
||||
@Override
|
||||
public DataSourceFactory getDataSourceFactory(CommaFeedConfiguration configuration) {
|
||||
return configuration.getDatabase();
|
||||
return configuration.getDataSourceFactory();
|
||||
}
|
||||
});
|
||||
|
||||
bootstrap.addBundle(new AssetsBundle("/assets/", "/", "index.html"));
|
||||
bootstrap.addBundle(new MultiPartBundle());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(CommaFeedConfiguration config, Environment environment) throws Exception {
|
||||
// configure context path
|
||||
environment.getApplicationContext().setContextPath(config.getApplicationSettings().getContextPath());
|
||||
|
||||
// guice init
|
||||
Injector injector = Guice.createInjector(new CommaFeedModule(hibernateBundle.getSessionFactory(), config, environment.metrics()));
|
||||
|
||||
// Auth/session management
|
||||
// session management
|
||||
environment.servlets().setSessionHandler(new SessionHandler(config.getSessionManagerFactory().build()));
|
||||
environment.jersey().register(new SecurityCheckUserServiceProvider(injector.getInstance(UserService.class)));
|
||||
environment.jersey().register(SecurityCheckProvider.class);
|
||||
environment.jersey().register(SessionHelperProvider.class);
|
||||
|
||||
// support for "@SecurityCheck User user" injection
|
||||
environment.jersey().register(new SecurityCheckFactoryProvider.Binder(injector.getInstance(UserService.class)));
|
||||
// support for "@Context SessionHelper sessionHelper" injection
|
||||
environment.jersey().register(new SessionHelperFactoryProvider.Binder());
|
||||
|
||||
// REST resources
|
||||
environment.jersey().setUrlPattern("/rest/*");
|
||||
((DefaultServerFactory) config.getServerFactory()).setJerseyRootPath("/rest/*");
|
||||
environment.jersey().register(injector.getInstance(AdminREST.class));
|
||||
environment.jersey().register(injector.getInstance(CategoryREST.class));
|
||||
environment.jersey().register(injector.getInstance(EntryREST.class));
|
||||
@@ -134,9 +136,13 @@ public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
|
||||
environment.servlets().addServlet("analytics.js", injector.getInstance(AnalyticsServlet.class)).addMapping("/analytics.js");
|
||||
|
||||
// Scheduled tasks
|
||||
ScheduledExecutorService executor = environment.lifecycle().scheduledExecutorService("task-scheduler").build();
|
||||
injector.getInstance(OldStatusesCleanupTask.class).register(executor);
|
||||
injector.getInstance(OrphansCleanupTask.class).register(executor);
|
||||
Set<ScheduledTask> tasks = injector.getInstance(Key.get(new TypeLiteral<Set<ScheduledTask>>() {
|
||||
}));
|
||||
ScheduledExecutorService executor = environment.lifecycle().scheduledExecutorService("task-scheduler", true).threads(tasks.size())
|
||||
.build();
|
||||
for (ScheduledTask task : tasks) {
|
||||
task.register(executor);
|
||||
}
|
||||
|
||||
// database init/changelogs
|
||||
environment.lifecycle().manage(injector.getInstance(StartupService.class));
|
||||
@@ -146,16 +152,6 @@ public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
|
||||
environment.lifecycle().manage(injector.getInstance(FeedRefreshWorker.class));
|
||||
environment.lifecycle().manage(injector.getInstance(FeedRefreshUpdater.class));
|
||||
|
||||
// Swagger
|
||||
environment.jersey().register(new ApiListingResourceJSON());
|
||||
environment.jersey().register(new ApiDeclarationProvider());
|
||||
environment.jersey().register(new ResourceListingProvider());
|
||||
ScannerFactory.setScanner(new DefaultJaxrsScanner());
|
||||
ClassReaders.setReader(new DefaultJaxrsApiReader());
|
||||
SwaggerConfig swaggerConfig = ConfigFactory.config();
|
||||
swaggerConfig.setApiVersion("1");
|
||||
swaggerConfig.setBasePath("/rest");
|
||||
|
||||
// cache configuration
|
||||
// prevent caching on REST resources, except for favicons
|
||||
environment.servlets().addFilter("cache-filter", new CacheBustingFilter() {
|
||||
@@ -170,8 +166,6 @@ public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
|
||||
}
|
||||
}).addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/rest/*");
|
||||
|
||||
// enable wadl
|
||||
environment.jersey().disable(ResourceConfig.FEATURE_DISABLE_WADL);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
@@ -12,7 +12,7 @@ import javax.validation.constraints.NotNull;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import org.apache.commons.lang.time.DateUtils;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import org.hibernate.validator.constraints.NotBlank;
|
||||
|
||||
import com.commafeed.backend.cache.RedisPoolFactory;
|
||||
@@ -35,7 +35,7 @@ public class CommaFeedConfiguration extends Configuration {
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty("database")
|
||||
private DataSourceFactory database = new DataSourceFactory();
|
||||
private DataSourceFactory dataSourceFactory = new DataSourceFactory();
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@@ -64,60 +64,82 @@ public class CommaFeedConfiguration extends Configuration {
|
||||
public static class ApplicationSettings {
|
||||
@NotNull
|
||||
@NotBlank
|
||||
private String contextPath;
|
||||
|
||||
@NotNull
|
||||
@NotBlank
|
||||
@Valid
|
||||
private String publicUrl;
|
||||
|
||||
@NotNull
|
||||
private boolean allowRegistrations;
|
||||
@Valid
|
||||
private Boolean allowRegistrations;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
private Boolean createDemoAccount;
|
||||
|
||||
private String googleAnalyticsTrackingCode;
|
||||
|
||||
@NotNull
|
||||
@Min(1)
|
||||
private int backgroundThreads;
|
||||
private String googleAuthKey;
|
||||
|
||||
@NotNull
|
||||
@Min(1)
|
||||
private int databaseUpdateThreads;
|
||||
@Valid
|
||||
private Integer backgroundThreads;
|
||||
|
||||
@NotNull
|
||||
@Min(1)
|
||||
@Valid
|
||||
private Integer databaseUpdateThreads;
|
||||
|
||||
private String smtpHost;
|
||||
|
||||
private int smtpPort;
|
||||
|
||||
private boolean smtpTls;
|
||||
|
||||
private String smtpUserName;
|
||||
|
||||
private String smtpPassword;
|
||||
private String smtpFromAddress;
|
||||
|
||||
private boolean graphiteEnabled;
|
||||
private String graphitePrefix;
|
||||
private String graphiteHost;
|
||||
private int graphitePort;
|
||||
private int graphiteInterval;
|
||||
|
||||
@NotNull
|
||||
private boolean heavyLoad;
|
||||
@Valid
|
||||
private Boolean heavyLoad;
|
||||
|
||||
@NotNull
|
||||
private boolean pubsubhubbub;
|
||||
@Valid
|
||||
private Boolean pubsubhubbub;
|
||||
|
||||
@NotNull
|
||||
private boolean imageProxyEnabled;
|
||||
@Valid
|
||||
private Boolean imageProxyEnabled;
|
||||
|
||||
@NotNull
|
||||
@Min(0)
|
||||
private int queryTimeout;
|
||||
@Valid
|
||||
private Integer queryTimeout;
|
||||
|
||||
@NotNull
|
||||
@Min(0)
|
||||
private int keepStatusDays;
|
||||
@Valid
|
||||
private Integer keepStatusDays;
|
||||
|
||||
@NotNull
|
||||
@Min(0)
|
||||
private int refreshIntervalMinutes;
|
||||
@Valid
|
||||
private Integer maxFeedCapacity;
|
||||
|
||||
@NotNull
|
||||
@Min(0)
|
||||
@Valid
|
||||
private Integer refreshIntervalMinutes;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
private CacheType cache;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
private String announcement;
|
||||
|
||||
public Date getUnreadThreshold() {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.commafeed;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -7,13 +9,23 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.hibernate.SessionFactory;
|
||||
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.codahale.metrics.graphite.Graphite;
|
||||
import com.codahale.metrics.graphite.GraphiteReporter;
|
||||
import com.codahale.metrics.MetricFilter;
|
||||
import com.commafeed.CommaFeedConfiguration.ApplicationSettings;
|
||||
import com.commafeed.CommaFeedConfiguration.CacheType;
|
||||
import com.commafeed.backend.cache.CacheService;
|
||||
import com.commafeed.backend.cache.NoopCacheService;
|
||||
import com.commafeed.backend.cache.RedisCacheService;
|
||||
import com.commafeed.backend.favicon.DefaultFaviconFetcher;
|
||||
import com.commafeed.backend.favicon.AbstractFaviconFetcher;
|
||||
import com.commafeed.backend.favicon.DefaultFaviconFetcher;
|
||||
import com.commafeed.backend.favicon.FacebookFaviconFetcher;
|
||||
import com.commafeed.backend.favicon.YoutubeFaviconFetcher;
|
||||
import com.commafeed.backend.task.OldEntriesCleanupTask;
|
||||
import com.commafeed.backend.task.OldStatusesCleanupTask;
|
||||
import com.commafeed.backend.task.OrphanedContentsCleanupTask;
|
||||
import com.commafeed.backend.task.OrphanedFeedsCleanupTask;
|
||||
import com.commafeed.backend.task.ScheduledTask;
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Provides;
|
||||
import com.google.inject.multibindings.Multibinder;
|
||||
@@ -38,8 +50,37 @@ public class CommaFeedModule extends AbstractModule {
|
||||
log.info("using cache {}", cacheService.getClass());
|
||||
bind(CacheService.class).toInstance(cacheService);
|
||||
|
||||
Multibinder<AbstractFaviconFetcher> multibinder = Multibinder.newSetBinder(binder(), AbstractFaviconFetcher.class);
|
||||
multibinder.addBinding().to(YoutubeFaviconFetcher.class);
|
||||
multibinder.addBinding().to(DefaultFaviconFetcher.class);
|
||||
Multibinder<AbstractFaviconFetcher> faviconMultibinder = Multibinder.newSetBinder(binder(), AbstractFaviconFetcher.class);
|
||||
faviconMultibinder.addBinding().to(YoutubeFaviconFetcher.class);
|
||||
faviconMultibinder.addBinding().to(FacebookFaviconFetcher.class);
|
||||
faviconMultibinder.addBinding().to(DefaultFaviconFetcher.class);
|
||||
|
||||
Multibinder<ScheduledTask> taskMultibinder = Multibinder.newSetBinder(binder(), ScheduledTask.class);
|
||||
taskMultibinder.addBinding().to(OldStatusesCleanupTask.class);
|
||||
taskMultibinder.addBinding().to(OldEntriesCleanupTask.class);
|
||||
taskMultibinder.addBinding().to(OrphanedFeedsCleanupTask.class);
|
||||
taskMultibinder.addBinding().to(OrphanedContentsCleanupTask.class);
|
||||
|
||||
ApplicationSettings settings = config.getApplicationSettings();
|
||||
|
||||
if (settings.isGraphiteEnabled()) {
|
||||
final String graphitePrefix = settings.getGraphitePrefix();
|
||||
final String graphiteHost = settings.getGraphiteHost();
|
||||
final int graphitePort = settings.getGraphitePort();
|
||||
final int graphiteInterval = settings.getGraphiteInterval();
|
||||
|
||||
log.info("Graphite Metrics will be sent to host={}, port={}, prefix={}, interval={}sec", graphiteHost, graphitePort, graphitePrefix, graphiteInterval);
|
||||
|
||||
final Graphite graphite = new Graphite(new InetSocketAddress(graphiteHost, graphitePort));
|
||||
final GraphiteReporter reporter = GraphiteReporter.forRegistry(metrics)
|
||||
.prefixedWith(graphitePrefix)
|
||||
.convertRatesTo(TimeUnit.SECONDS)
|
||||
.convertDurationsTo(TimeUnit.MILLISECONDS)
|
||||
.filter(MetricFilter.ALL)
|
||||
.build(graphite);
|
||||
reporter.start(graphiteInterval, TimeUnit.SECONDS);
|
||||
} else {
|
||||
log.info("Graphite Metrics Disabled. Metrics will not be sent.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import org.apache.http.entity.HttpEntityWrapper;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
|
||||
class ContentEncodingInterceptor implements HttpResponseInterceptor {
|
||||
|
||||
|
||||
private static final Set<String> ALLOWED_CONTENT_ENCODINGS = new HashSet<>(Arrays.asList("gzip", "x-gzip", "deflate", "identity"));
|
||||
|
||||
@Override
|
||||
@@ -28,17 +28,17 @@ class ContentEncodingInterceptor implements HttpResponseInterceptor {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean containsUnsupportedEncodings(Header contentEncodingHeader) {
|
||||
HeaderElement[] codecs = contentEncodingHeader.getElements();
|
||||
|
||||
|
||||
for (final HeaderElement codec : codecs) {
|
||||
String codecName = codec.getName().toLowerCase(Locale.US);
|
||||
if (!ALLOWED_CONTENT_ENCODINGS.contains(codecName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -47,9 +47,9 @@ class ContentEncodingInterceptor implements HttpResponseInterceptor {
|
||||
@Override
|
||||
public Header getContentEncoding() {
|
||||
return null;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
response.setEntity(wrapped);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.http.Consts;
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpEntity;
|
||||
@@ -34,7 +34,7 @@ import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.client.protocol.HttpClientContext;
|
||||
import org.apache.http.config.ConnectionConfig;
|
||||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
||||
import org.apache.http.conn.ssl.NoopHostnameVerifier;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
@@ -53,7 +53,7 @@ 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 HttpResponseInterceptor REMOVE_INCORRECT_CONTENT_ENCODING = new ContentEncodingInterceptor();
|
||||
|
||||
private static SSLContext SSL_CONTEXT = null;
|
||||
@@ -70,7 +70,7 @@ public class HttpGetter {
|
||||
|
||||
@Inject
|
||||
public HttpGetter(CommaFeedConfiguration config) {
|
||||
this.userAgent = String.format("CommaFeed/%s (https://www.commafeed.com)", config.getVersion());
|
||||
this.userAgent = String.format("CommaFeed/%s (https://github.com/Athou/commafeed)", config.getVersion());
|
||||
}
|
||||
|
||||
public HttpResult getBinary(String url, int timeout) throws ClientProtocolException, IOException, NotModifiedException {
|
||||
@@ -181,8 +181,8 @@ public class HttpGetter {
|
||||
builder.addInterceptorFirst(REMOVE_INCORRECT_CONTENT_ENCODING);
|
||||
builder.disableAutomaticRetries();
|
||||
|
||||
builder.setSslcontext(SSL_CONTEXT);
|
||||
builder.setHostnameVerifier(SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
|
||||
builder.setSSLContext(SSL_CONTEXT);
|
||||
builder.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE);
|
||||
|
||||
RequestConfig.Builder configBuilder = RequestConfig.custom();
|
||||
configBuilder.setCookieSpec(CookieSpecs.IGNORE_COOKIES);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.commafeed.backend.cache;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -18,7 +19,6 @@ import com.commafeed.frontend.model.Category;
|
||||
import com.commafeed.frontend.model.UnreadCount;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@@ -30,7 +30,7 @@ public class RedisCacheService extends CacheService {
|
||||
|
||||
@Override
|
||||
public List<String> getLastEntries(Feed feed) {
|
||||
List<String> list = Lists.newArrayList();
|
||||
List<String> list = new ArrayList<>();
|
||||
try (Jedis jedis = pool.getResource()) {
|
||||
String key = buildRedisEntryKey(feed);
|
||||
Set<String> members = jedis.smembers(key);
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.commafeed.backend.cache;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import redis.clients.jedis.JedisPool;
|
||||
import redis.clients.jedis.JedisPoolConfig;
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.lang.ObjectUtils;
|
||||
import org.hibernate.SessionFactory;
|
||||
|
||||
import com.commafeed.backend.model.FeedCategory;
|
||||
import com.commafeed.backend.model.QFeedCategory;
|
||||
import com.commafeed.backend.model.QUser;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.mysema.query.types.Predicate;
|
||||
import com.querydsl.core.types.Predicate;
|
||||
|
||||
@Singleton
|
||||
public class FeedCategoryDAO extends GenericDAO<FeedCategory> {
|
||||
@@ -26,11 +26,11 @@ public class FeedCategoryDAO extends GenericDAO<FeedCategory> {
|
||||
}
|
||||
|
||||
public List<FeedCategory> findAll(User user) {
|
||||
return newQuery().from(category).where(category.user.eq(user)).join(category.user, QUser.user).fetch().list(category);
|
||||
return query().selectFrom(category).where(category.user.eq(user)).join(category.user, QUser.user).fetchJoin().fetch();
|
||||
}
|
||||
|
||||
public FeedCategory findById(User user, Long id) {
|
||||
return newQuery().from(category).where(category.user.eq(user), category.id.eq(id)).uniqueResult(category);
|
||||
return query().selectFrom(category).where(category.user.eq(user), category.id.eq(id)).fetchOne();
|
||||
}
|
||||
|
||||
public FeedCategory findByName(User user, String name, FeedCategory parent) {
|
||||
@@ -40,7 +40,7 @@ public class FeedCategoryDAO extends GenericDAO<FeedCategory> {
|
||||
} else {
|
||||
parentPredicate = category.parent.eq(parent);
|
||||
}
|
||||
return newQuery().from(category).where(category.user.eq(user), category.name.eq(name), parentPredicate).uniqueResult(category);
|
||||
return query().selectFrom(category).where(category.user.eq(user), category.name.eq(name), parentPredicate).fetchOne();
|
||||
}
|
||||
|
||||
public List<FeedCategory> findByParent(User user, FeedCategory parent) {
|
||||
@@ -50,18 +50,11 @@ public class FeedCategoryDAO extends GenericDAO<FeedCategory> {
|
||||
} else {
|
||||
parentPredicate = category.parent.eq(parent);
|
||||
}
|
||||
return newQuery().from(category).where(category.user.eq(user), parentPredicate).list(category);
|
||||
return query().selectFrom(category).where(category.user.eq(user), parentPredicate).fetch();
|
||||
}
|
||||
|
||||
public List<FeedCategory> findAllChildrenCategories(User user, FeedCategory parent) {
|
||||
List<FeedCategory> list = Lists.newArrayList();
|
||||
List<FeedCategory> all = findAll(user);
|
||||
for (FeedCategory cat : all) {
|
||||
if (isChild(cat, parent)) {
|
||||
list.add(cat);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
return findAll(user).stream().filter(c -> isChild(c, parent)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private boolean isChild(FeedCategory child, FeedCategory parent) {
|
||||
@@ -70,7 +63,7 @@ public class FeedCategoryDAO extends GenericDAO<FeedCategory> {
|
||||
}
|
||||
boolean isChild = false;
|
||||
while (child != null) {
|
||||
if (ObjectUtils.equals(child.getId(), parent.getId())) {
|
||||
if (Objects.equals(child.getId(), parent.getId())) {
|
||||
isChild = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hibernate.SessionFactory;
|
||||
|
||||
import com.commafeed.backend.model.Feed;
|
||||
@@ -15,8 +15,9 @@ import com.commafeed.backend.model.QFeed;
|
||||
import com.commafeed.backend.model.QFeedSubscription;
|
||||
import com.commafeed.backend.model.QUser;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.mysema.query.BooleanBuilder;
|
||||
import com.mysema.query.jpa.hibernate.HibernateQuery;
|
||||
import com.querydsl.jpa.JPAExpressions;
|
||||
import com.querydsl.jpa.JPQLQuery;
|
||||
import com.querydsl.jpa.hibernate.HibernateQuery;
|
||||
|
||||
@Singleton
|
||||
public class FeedDAO extends GenericDAO<Feed> {
|
||||
@@ -29,24 +30,23 @@ public class FeedDAO extends GenericDAO<Feed> {
|
||||
}
|
||||
|
||||
public List<Feed> findNextUpdatable(int count, Date lastLoginThreshold) {
|
||||
BooleanBuilder disabledDatePredicate = new BooleanBuilder();
|
||||
disabledDatePredicate.or(feed.disabledUntil.isNull());
|
||||
disabledDatePredicate.or(feed.disabledUntil.lt(new Date()));
|
||||
HibernateQuery<Feed> query = query().selectFrom(feed);
|
||||
query.where(feed.disabledUntil.isNull().or(feed.disabledUntil.lt(new Date())));
|
||||
|
||||
HibernateQuery query = newQuery().from(feed);
|
||||
if (lastLoginThreshold != null) {
|
||||
QFeedSubscription subs = QFeedSubscription.feedSubscription;
|
||||
QUser user = QUser.user;
|
||||
query.join(feed.subscriptions, subs).join(subs.user, user).where(disabledDatePredicate, user.lastLogin.gt(lastLoginThreshold));
|
||||
} else {
|
||||
query.where(disabledDatePredicate);
|
||||
|
||||
JPQLQuery<Integer> subQuery = JPAExpressions.selectOne().from(subs);
|
||||
subQuery.join(subs.user, user).where(user.lastLogin.gt(lastLoginThreshold));
|
||||
query.where(subQuery.exists());
|
||||
}
|
||||
|
||||
return query.orderBy(feed.disabledUntil.asc()).limit(count).distinct().list(feed);
|
||||
return query.orderBy(feed.disabledUntil.asc()).limit(count).distinct().fetch();
|
||||
}
|
||||
|
||||
public Feed findByUrl(String normalizedUrl) {
|
||||
List<Feed> feeds = newQuery().from(feed).where(feed.normalizedUrlHash.eq(DigestUtils.sha1Hex(normalizedUrl))).list(feed);
|
||||
List<Feed> feeds = query().selectFrom(feed).where(feed.normalizedUrlHash.eq(DigestUtils.sha1Hex(normalizedUrl))).fetch();
|
||||
Feed feed = Iterables.getFirst(feeds, null);
|
||||
if (feed != null && StringUtils.equals(normalizedUrl, feed.getNormalizedUrl())) {
|
||||
return feed;
|
||||
@@ -55,11 +55,12 @@ public class FeedDAO extends GenericDAO<Feed> {
|
||||
}
|
||||
|
||||
public List<Feed> findByTopic(String topic) {
|
||||
return newQuery().from(feed).where(feed.pushTopicHash.eq(DigestUtils.sha1Hex(topic))).list(feed);
|
||||
return query().selectFrom(feed).where(feed.pushTopicHash.eq(DigestUtils.sha1Hex(topic))).fetch();
|
||||
}
|
||||
|
||||
public List<Feed> findWithoutSubscriptions(int max) {
|
||||
QFeedSubscription sub = QFeedSubscription.feedSubscription;
|
||||
return newQuery().from(feed).leftJoin(feed.subscriptions, sub).where(sub.id.isNull()).limit(max).list(feed);
|
||||
return query().selectFrom(feed).where(JPAExpressions.selectOne().from(sub).where(sub.feed.eq(feed)).notExists()).limit(max)
|
||||
.fetch();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,14 @@ import org.hibernate.SessionFactory;
|
||||
import com.commafeed.backend.model.FeedEntryContent;
|
||||
import com.commafeed.backend.model.QFeedEntry;
|
||||
import com.commafeed.backend.model.QFeedEntryContent;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.querydsl.jpa.JPAExpressions;
|
||||
import com.querydsl.jpa.JPQLQuery;
|
||||
|
||||
@Singleton
|
||||
public class FeedEntryContentDAO extends GenericDAO<FeedEntryContent> {
|
||||
|
||||
private QFeedEntryContent content = QFeedEntryContent.feedEntryContent;
|
||||
private QFeedEntry entry = QFeedEntry.feedEntry;
|
||||
|
||||
@Inject
|
||||
public FeedEntryContentDAO(SessionFactory sessionFactory) {
|
||||
@@ -23,15 +25,15 @@ public class FeedEntryContentDAO extends GenericDAO<FeedEntryContent> {
|
||||
}
|
||||
|
||||
public Long findExisting(String contentHash, String titleHash) {
|
||||
List<Long> list = newQuery().from(content).where(content.contentHash.eq(contentHash), content.titleHash.eq(titleHash)).limit(1)
|
||||
.list(content.id);
|
||||
return Iterables.getFirst(list, null);
|
||||
return query().select(content.id).from(content).where(content.contentHash.eq(contentHash), content.titleHash.eq(titleHash))
|
||||
.fetchFirst();
|
||||
}
|
||||
|
||||
public int deleteWithoutEntries(int max) {
|
||||
QFeedEntry entry = QFeedEntry.feedEntry;
|
||||
List<FeedEntryContent> list = newQuery().from(content).leftJoin(content.entries, entry).where(entry.id.isNull()).limit(max)
|
||||
.list(content);
|
||||
|
||||
JPQLQuery<Integer> subQuery = JPAExpressions.selectOne().from(entry).where(entry.content.id.eq(content.id));
|
||||
List<FeedEntryContent> list = query().selectFrom(content).where(subQuery.notExists()).limit(max).fetch();
|
||||
|
||||
int deleted = list.size();
|
||||
delete(list);
|
||||
return deleted;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -11,10 +11,12 @@ import org.hibernate.SessionFactory;
|
||||
|
||||
import com.commafeed.backend.model.Feed;
|
||||
import com.commafeed.backend.model.FeedEntry;
|
||||
import com.commafeed.backend.model.QFeed;
|
||||
import com.commafeed.backend.model.QFeedEntry;
|
||||
import com.commafeed.backend.model.QFeedSubscription;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.querydsl.core.Tuple;
|
||||
import com.querydsl.core.types.dsl.NumberExpression;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Singleton
|
||||
public class FeedEntryDAO extends GenericDAO<FeedEntry> {
|
||||
@@ -27,22 +29,32 @@ public class FeedEntryDAO extends GenericDAO<FeedEntry> {
|
||||
}
|
||||
|
||||
public Long findExisting(String guid, Feed feed) {
|
||||
List<Long> list = newQuery().from(entry).where(entry.guidHash.eq(DigestUtils.sha1Hex(guid)), entry.feed.eq(feed)).limit(1)
|
||||
.list(entry.id);
|
||||
return Iterables.getFirst(list, null);
|
||||
return query().select(entry.id).from(entry).where(entry.guidHash.eq(DigestUtils.sha1Hex(guid)), entry.feed.eq(feed)).limit(1)
|
||||
.fetchOne();
|
||||
}
|
||||
|
||||
public List<FeedEntry> findWithoutSubscriptions(int max) {
|
||||
QFeed feed = QFeed.feed;
|
||||
QFeedSubscription sub = QFeedSubscription.feedSubscription;
|
||||
return newQuery().from(entry).join(entry.feed, feed).leftJoin(feed.subscriptions, sub).where(sub.id.isNull()).limit(max)
|
||||
.list(entry);
|
||||
public List<FeedCapacity> findFeedsExceedingCapacity(long maxCapacity, long max) {
|
||||
NumberExpression<Long> count = entry.id.count();
|
||||
List<Tuple> tuples = query().select(entry.feed.id, count).from(entry).groupBy(entry.feed).having(count.gt(maxCapacity)).limit(max)
|
||||
.fetch();
|
||||
return tuples.stream().map(t -> new FeedCapacity(t.get(entry.feed.id), t.get(count))).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public int delete(Date olderThan, int max) {
|
||||
List<FeedEntry> list = newQuery().from(entry).where(entry.inserted.lt(olderThan)).limit(max).list(entry);
|
||||
int deleted = list.size();
|
||||
delete(list);
|
||||
return deleted;
|
||||
public int delete(Long feedId, long max) {
|
||||
|
||||
List<FeedEntry> list = query().selectFrom(entry).where(entry.feed.id.eq(feedId)).limit(max).fetch();
|
||||
return delete(list);
|
||||
}
|
||||
|
||||
public int deleteOldEntries(Long feedId, long max) {
|
||||
List<FeedEntry> list = query().selectFrom(entry).where(entry.feed.id.eq(feedId)).orderBy(entry.updated.asc()).limit(max).fetch();
|
||||
return delete(list);
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public static class FeedCapacity {
|
||||
private Long id;
|
||||
private Long capacity;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@@ -7,13 +8,16 @@ import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang.builder.CompareToBuilder;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.builder.CompareToBuilder;
|
||||
import org.hibernate.SessionFactory;
|
||||
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.backend.FixedSizeSortedSet;
|
||||
import com.commafeed.backend.feed.FeedEntryKeyword;
|
||||
import com.commafeed.backend.feed.FeedEntryKeyword.Mode;
|
||||
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;
|
||||
@@ -26,11 +30,10 @@ import com.commafeed.backend.model.User;
|
||||
import com.commafeed.backend.model.UserSettings.ReadingOrder;
|
||||
import com.commafeed.frontend.model.UnreadCount;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Ordering;
|
||||
import com.mysema.query.BooleanBuilder;
|
||||
import com.mysema.query.Tuple;
|
||||
import com.mysema.query.jpa.hibernate.HibernateQuery;
|
||||
import com.querydsl.core.BooleanBuilder;
|
||||
import com.querydsl.core.Tuple;
|
||||
import com.querydsl.jpa.hibernate.HibernateQuery;
|
||||
|
||||
@Singleton
|
||||
public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
||||
@@ -60,13 +63,26 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
||||
builder.append(o2.getEntryUpdated(), o1.getEntryUpdated());
|
||||
builder.append(o2.getId(), o1.getId());
|
||||
return builder.toComparison();
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
private static final Comparator<FeedEntryStatus> STATUS_COMPARATOR_ASC = Ordering.from(STATUS_COMPARATOR_DESC).reverse();
|
||||
|
||||
private static final Comparator<FeedEntryStatus> STATUS_COMPARATOR_ABC = new Comparator<FeedEntryStatus>() {
|
||||
@Override
|
||||
public int compare(FeedEntryStatus o1, FeedEntryStatus o2) {
|
||||
CompareToBuilder builder = new CompareToBuilder();
|
||||
builder.append(o1.getEntry().getContent().getTitle(), o2.getEntry().getContent().getTitle());
|
||||
builder.append(o1.getId(), o2.getId());
|
||||
return builder.toComparison();
|
||||
}
|
||||
};
|
||||
|
||||
private static final Comparator<FeedEntryStatus> STATUS_COMPARATOR_ZYX = Ordering.from(STATUS_COMPARATOR_ABC).reverse();
|
||||
|
||||
|
||||
public FeedEntryStatus getStatus(User user, FeedSubscription sub, FeedEntry entry) {
|
||||
List<FeedEntryStatus> statuses = newQuery().from(status).where(status.entry.eq(entry), status.subscription.eq(sub)).list(status);
|
||||
List<FeedEntryStatus> statuses = query().selectFrom(status).where(status.entry.eq(entry), status.subscription.eq(sub)).fetch();
|
||||
FeedEntryStatus status = Iterables.getFirst(statuses, null);
|
||||
return handleStatus(user, status, sub, entry);
|
||||
}
|
||||
@@ -91,20 +107,28 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
||||
}
|
||||
|
||||
public List<FeedEntryStatus> findStarred(User user, Date newerThan, int offset, int limit, ReadingOrder order, boolean includeContent) {
|
||||
HibernateQuery query = newQuery().from(status).where(status.user.eq(user), status.starred.isTrue());
|
||||
HibernateQuery<FeedEntryStatus> query = query().selectFrom(status).where(status.user.eq(user), status.starred.isTrue());
|
||||
if (newerThan != null) {
|
||||
query.where(status.entryInserted.gt(newerThan));
|
||||
}
|
||||
|
||||
if (order == ReadingOrder.asc) {
|
||||
query.orderBy(status.entryUpdated.asc(), status.id.asc());
|
||||
} else {
|
||||
} else if (order == ReadingOrder.desc){
|
||||
query.orderBy(status.entryUpdated.desc(), status.id.desc());
|
||||
} else if (order == ReadingOrder.abc) {
|
||||
query.orderBy(status.entry.content.title.asc(), status.id.desc());
|
||||
} else { //order == ReadingOrder.xyz
|
||||
query.orderBy(status.entry.content.title.desc(), status.id.desc());
|
||||
}
|
||||
|
||||
query.offset(offset).limit(limit).setTimeout(config.getApplicationSettings().getQueryTimeout());
|
||||
query.offset(offset).limit(limit);
|
||||
int timeout = config.getApplicationSettings().getQueryTimeout();
|
||||
if (timeout > 0) {
|
||||
query.setTimeout(timeout / 1000);
|
||||
}
|
||||
|
||||
List<FeedEntryStatus> statuses = query.list(status);
|
||||
List<FeedEntryStatus> statuses = query.fetch();
|
||||
for (FeedEntryStatus status : statuses) {
|
||||
status = handleStatus(user, status, status.getSubscription(), status.getEntry());
|
||||
fetchTags(user, status);
|
||||
@@ -112,18 +136,21 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
||||
return lazyLoadContent(includeContent, statuses);
|
||||
}
|
||||
|
||||
private HibernateQuery buildQuery(User user, FeedSubscription sub, boolean unreadOnly, String keywords, Date newerThan, int offset,
|
||||
int limit, ReadingOrder order, Date last, String tag) {
|
||||
private HibernateQuery<FeedEntry> buildQuery(User user, FeedSubscription sub, boolean unreadOnly, List<FeedEntryKeyword> keywords,
|
||||
Date newerThan, int offset, int limit, ReadingOrder order, FeedEntryStatus last, String tag) {
|
||||
|
||||
HibernateQuery query = newQuery().from(entry).where(entry.feed.eq(sub.getFeed()));
|
||||
HibernateQuery<FeedEntry> query = query().selectFrom(entry).where(entry.feed.eq(sub.getFeed()));
|
||||
|
||||
if (keywords != null) {
|
||||
if (CollectionUtils.isNotEmpty(keywords)) {
|
||||
query.join(entry.content, content);
|
||||
|
||||
for (String keyword : StringUtils.split(keywords)) {
|
||||
for (FeedEntryKeyword keyword : keywords) {
|
||||
BooleanBuilder or = new BooleanBuilder();
|
||||
or.or(content.content.containsIgnoreCase(keyword));
|
||||
or.or(content.title.containsIgnoreCase(keyword));
|
||||
or.or(content.content.containsIgnoreCase(keyword.getKeyword()));
|
||||
or.or(content.title.containsIgnoreCase(keyword.getKeyword()));
|
||||
if (keyword.getMode() == Mode.EXCLUDE) {
|
||||
or.not();
|
||||
}
|
||||
query.where(or);
|
||||
}
|
||||
}
|
||||
@@ -154,17 +181,29 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
||||
|
||||
if (last != null) {
|
||||
if (order == ReadingOrder.desc) {
|
||||
query.where(entry.updated.gt(last));
|
||||
} else {
|
||||
query.where(entry.updated.lt(last));
|
||||
query.where(entry.updated.gt(last.getEntryUpdated()));
|
||||
} else if (order == ReadingOrder.asc) {
|
||||
query.where(entry.updated.lt(last.getEntryUpdated()));
|
||||
} else if (order == ReadingOrder.abc) {
|
||||
query.join(entry.content, content);
|
||||
query.where(content.title.lt(last.getEntry().getContent().getTitle()));
|
||||
} else { //order == ReadingOrder.zyx
|
||||
query.join(entry.content, content);
|
||||
query.where(content.title.gt(last.getEntry().getContent().getTitle()));
|
||||
}
|
||||
} else if (order != null && (order == ReadingOrder.abc || order == ReadingOrder.zyx)) {
|
||||
query.join(entry.content, content);
|
||||
}
|
||||
|
||||
|
||||
if (order != null) {
|
||||
if (order == ReadingOrder.asc) {
|
||||
query.orderBy(entry.updated.asc(), entry.id.asc());
|
||||
} else {
|
||||
} else if (order == ReadingOrder.desc) {
|
||||
query.orderBy(entry.updated.desc(), entry.id.desc());
|
||||
} else if (order == ReadingOrder.abc) {
|
||||
query.orderBy(content.title.asc(), entry.id.asc());
|
||||
} else { //order == ReadingOrder.zyx
|
||||
query.orderBy(content.title.desc(), entry.id.desc());
|
||||
}
|
||||
}
|
||||
if (offset > -1) {
|
||||
@@ -180,23 +219,40 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
||||
return query;
|
||||
}
|
||||
|
||||
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) {
|
||||
public List<FeedEntryStatus> findBySubscriptions(User user, List<FeedSubscription> subs, boolean unreadOnly,
|
||||
List<FeedEntryKeyword> 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;
|
||||
|
||||
Comparator<FeedEntryStatus> comparator;
|
||||
if (order == ReadingOrder.desc) {
|
||||
comparator = STATUS_COMPARATOR_DESC;
|
||||
} else if (order == ReadingOrder.abc) {
|
||||
comparator = STATUS_COMPARATOR_ABC;
|
||||
} else if (order == ReadingOrder.zyx) {
|
||||
comparator = STATUS_COMPARATOR_ZYX;
|
||||
} else {
|
||||
comparator = 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;
|
||||
HibernateQuery query = buildQuery(user, sub, unreadOnly, keywords, newerThan, -1, capacity, order, last, tag);
|
||||
List<Tuple> tuples = query.list(entry.id, entry.updated, status.id);
|
||||
FeedEntryStatus last = (order != null && set.isFull()) ? set.last() : null;
|
||||
HibernateQuery<FeedEntry> query = buildQuery(user, sub, unreadOnly, keywords, newerThan, -1, capacity, order, last, tag);
|
||||
List<Tuple> tuples = query.select(entry.id, entry.updated, status.id, entry.content.title).fetch();
|
||||
|
||||
for (Tuple tuple : tuples) {
|
||||
Long id = tuple.get(entry.id);
|
||||
Date updated = tuple.get(entry.updated);
|
||||
Long statusId = tuple.get(status.id);
|
||||
|
||||
FeedEntryContent content = new FeedEntryContent();
|
||||
content.setTitle(tuple.get(entry.content.title));
|
||||
|
||||
FeedEntry entry = new FeedEntry();
|
||||
entry.setId(id);
|
||||
entry.setUpdated(updated);
|
||||
entry.setContent(content);
|
||||
|
||||
FeedEntryStatus status = new FeedEntryStatus();
|
||||
status.setId(statusId);
|
||||
@@ -211,7 +267,7 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
||||
List<FeedEntryStatus> placeholders = set.asList();
|
||||
int size = placeholders.size();
|
||||
if (size < offset) {
|
||||
return Lists.newArrayList();
|
||||
return new ArrayList<>();
|
||||
}
|
||||
placeholders = placeholders.subList(Math.max(offset, 0), size);
|
||||
|
||||
@@ -219,7 +275,7 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
||||
if (onlyIds) {
|
||||
statuses = placeholders;
|
||||
} else {
|
||||
statuses = Lists.newArrayList();
|
||||
statuses = new ArrayList<>();
|
||||
for (FeedEntryStatus placeholder : placeholders) {
|
||||
Long statusId = placeholder.getId();
|
||||
FeedEntry entry = feedEntryDAO.findById(placeholder.getEntry().getId());
|
||||
@@ -235,8 +291,8 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
||||
|
||||
public UnreadCount getUnreadCount(User user, FeedSubscription subscription) {
|
||||
UnreadCount uc = null;
|
||||
HibernateQuery query = buildQuery(user, subscription, true, null, null, -1, -1, null, null, null);
|
||||
List<Tuple> tuples = query.list(entry.count(), entry.updated.max());
|
||||
HibernateQuery<FeedEntry> query = buildQuery(user, subscription, true, null, null, -1, -1, null, null, null);
|
||||
List<Tuple> tuples = query.select(entry.count(), entry.updated.max()).fetch();
|
||||
for (Tuple tuple : tuples) {
|
||||
Long count = tuple.get(entry.count());
|
||||
Date updated = tuple.get(entry.updated.max());
|
||||
@@ -256,7 +312,7 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
||||
}
|
||||
|
||||
public List<FeedEntryStatus> getOldStatuses(Date olderThan, int limit) {
|
||||
return newQuery().from(status).where(status.entryInserted.lt(olderThan), status.starred.isFalse()).limit(limit).list(status);
|
||||
return query().selectFrom(status).where(status.entryInserted.lt(olderThan), status.starred.isFalse()).limit(limit).fetch();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,10 +23,10 @@ public class FeedEntryTagDAO extends GenericDAO<FeedEntryTag> {
|
||||
}
|
||||
|
||||
public List<String> findByUser(User user) {
|
||||
return newQuery().from(tag).where(tag.user.eq(user)).distinct().list(tag.name);
|
||||
return query().selectDistinct(tag.name).from(tag).where(tag.user.eq(user)).fetch();
|
||||
}
|
||||
|
||||
public List<FeedEntryTag> findByEntry(User user, FeedEntry entry) {
|
||||
return newQuery().from(tag).where(tag.user.eq(user), tag.entry.eq(entry)).list(tag);
|
||||
return query().selectFrom(tag).where(tag.user.eq(user), tag.entry.eq(entry)).fetch();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -13,10 +15,8 @@ import com.commafeed.backend.model.FeedSubscription;
|
||||
import com.commafeed.backend.model.Models;
|
||||
import com.commafeed.backend.model.QFeedSubscription;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.mysema.query.jpa.hibernate.HibernateQuery;
|
||||
import com.querydsl.jpa.hibernate.HibernateQuery;
|
||||
|
||||
@Singleton
|
||||
public class FeedSubscriptionDAO extends GenericDAO<FeedSubscription> {
|
||||
@@ -29,57 +29,44 @@ public class FeedSubscriptionDAO extends GenericDAO<FeedSubscription> {
|
||||
}
|
||||
|
||||
public FeedSubscription findById(User user, Long id) {
|
||||
List<FeedSubscription> subs = newQuery().from(sub).where(sub.user.eq(user), sub.id.eq(id)).leftJoin(sub.feed).fetch()
|
||||
.leftJoin(sub.category).fetch().list(sub);
|
||||
List<FeedSubscription> subs = query().selectFrom(sub).where(sub.user.eq(user), sub.id.eq(id)).leftJoin(sub.feed).fetchJoin()
|
||||
.leftJoin(sub.category).fetchJoin().fetch();
|
||||
return initRelations(Iterables.getFirst(subs, null));
|
||||
}
|
||||
|
||||
public List<FeedSubscription> findByFeed(Feed feed) {
|
||||
return newQuery().from(sub).where(sub.feed.eq(feed)).list(sub);
|
||||
return query().selectFrom(sub).where(sub.feed.eq(feed)).fetch();
|
||||
}
|
||||
|
||||
public FeedSubscription findByFeed(User user, Feed feed) {
|
||||
List<FeedSubscription> subs = newQuery().from(sub).where(sub.user.eq(user), sub.feed.eq(feed)).list(sub);
|
||||
List<FeedSubscription> subs = query().selectFrom(sub).where(sub.user.eq(user), sub.feed.eq(feed)).fetch();
|
||||
return initRelations(Iterables.getFirst(subs, null));
|
||||
}
|
||||
|
||||
public List<FeedSubscription> findAll(User user) {
|
||||
List<FeedSubscription> subs = newQuery().from(sub).where(sub.user.eq(user)).leftJoin(sub.feed).fetch().leftJoin(sub.category)
|
||||
.fetch().list(sub);
|
||||
List<FeedSubscription> subs = query().selectFrom(sub).where(sub.user.eq(user)).leftJoin(sub.feed).fetchJoin()
|
||||
.leftJoin(sub.category).fetchJoin().fetch();
|
||||
return initRelations(subs);
|
||||
}
|
||||
|
||||
public List<FeedSubscription> findByCategory(User user, FeedCategory category) {
|
||||
HibernateQuery query = newQuery().from(sub).where(sub.user.eq(user));
|
||||
HibernateQuery<FeedSubscription> query = query().selectFrom(sub).where(sub.user.eq(user));
|
||||
if (category == null) {
|
||||
query.where(sub.category.isNull());
|
||||
} else {
|
||||
query.where(sub.category.eq(category));
|
||||
}
|
||||
return initRelations(query.list(sub));
|
||||
return initRelations(query.fetch());
|
||||
}
|
||||
|
||||
public List<FeedSubscription> findByCategories(User user, List<FeedCategory> categories) {
|
||||
List<Long> categoryIds = Lists.transform(categories, new Function<FeedCategory, Long>() {
|
||||
@Override
|
||||
public Long apply(FeedCategory input) {
|
||||
return input.getId();
|
||||
}
|
||||
});
|
||||
|
||||
List<FeedSubscription> subscriptions = Lists.newArrayList();
|
||||
for (FeedSubscription sub : findAll(user)) {
|
||||
if (sub.getCategory() != null && categoryIds.contains(sub.getCategory().getId())) {
|
||||
subscriptions.add(sub);
|
||||
}
|
||||
}
|
||||
return subscriptions;
|
||||
Set<Long> categoryIds = categories.stream().map(c -> c.getId()).collect(Collectors.toSet());
|
||||
return findAll(user).stream().filter(s -> s.getCategory() != null && categoryIds.contains(s.getCategory().getId()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<FeedSubscription> initRelations(List<FeedSubscription> list) {
|
||||
for (FeedSubscription sub : list) {
|
||||
initRelations(sub);
|
||||
}
|
||||
list.forEach(s -> initRelations(s));
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import io.dropwizard.hibernate.AbstractDAO;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.hibernate.SessionFactory;
|
||||
|
||||
import com.commafeed.backend.model.AbstractModel;
|
||||
import com.mysema.query.jpa.hibernate.HibernateQuery;
|
||||
import com.querydsl.jpa.hibernate.HibernateQueryFactory;
|
||||
|
||||
import io.dropwizard.hibernate.AbstractDAO;
|
||||
|
||||
public abstract class GenericDAO<T extends AbstractModel> extends AbstractDAO<T> {
|
||||
|
||||
private HibernateQueryFactory factory;
|
||||
|
||||
protected GenericDAO(SessionFactory sessionFactory) {
|
||||
super(sessionFactory);
|
||||
this.factory = new HibernateQueryFactory(() -> currentSession());
|
||||
}
|
||||
|
||||
protected HibernateQuery newQuery() {
|
||||
return new HibernateQuery(currentSession());
|
||||
protected HibernateQueryFactory query() {
|
||||
return factory;
|
||||
}
|
||||
|
||||
public void saveOrUpdate(T model) {
|
||||
@@ -24,21 +27,13 @@ public abstract class GenericDAO<T extends AbstractModel> extends AbstractDAO<T>
|
||||
}
|
||||
|
||||
public void saveOrUpdate(Collection<T> models) {
|
||||
for (T model : models) {
|
||||
persist(model);
|
||||
}
|
||||
models.forEach(m -> persist(m));
|
||||
}
|
||||
|
||||
public void merge(T model) {
|
||||
public void update(T model) {
|
||||
currentSession().merge(model);
|
||||
}
|
||||
|
||||
public void merge(Collection<T> models) {
|
||||
for (T model : models) {
|
||||
merge(model);
|
||||
}
|
||||
}
|
||||
|
||||
public T findById(Long id) {
|
||||
return get(id);
|
||||
}
|
||||
@@ -50,9 +45,7 @@ public abstract class GenericDAO<T extends AbstractModel> extends AbstractDAO<T>
|
||||
}
|
||||
|
||||
public int delete(Collection<T> objects) {
|
||||
for (T object : objects) {
|
||||
delete(object);
|
||||
}
|
||||
objects.forEach(o -> delete(o));
|
||||
return objects.size();
|
||||
}
|
||||
|
||||
|
||||
@@ -5,17 +5,26 @@ import org.hibernate.SessionFactory;
|
||||
import org.hibernate.Transaction;
|
||||
import org.hibernate.context.internal.ManagedSessionContext;
|
||||
|
||||
public abstract class UnitOfWork<T> {
|
||||
public class UnitOfWork {
|
||||
|
||||
private SessionFactory sessionFactory;
|
||||
|
||||
public UnitOfWork(SessionFactory sessionFactory) {
|
||||
this.sessionFactory = sessionFactory;
|
||||
@FunctionalInterface
|
||||
public static interface SessionRunner {
|
||||
public void runInSession();
|
||||
}
|
||||
|
||||
protected abstract T runInSession() throws Exception;
|
||||
@FunctionalInterface
|
||||
public static interface SessionRunnerReturningValue<T> {
|
||||
public T runInSession();
|
||||
}
|
||||
|
||||
public T run() {
|
||||
public static void run(SessionFactory sessionFactory, SessionRunner sessionRunner) {
|
||||
call(sessionFactory, () -> {
|
||||
sessionRunner.runInSession();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public static <T> T call(SessionFactory sessionFactory, SessionRunnerReturningValue<T> sessionRunner) {
|
||||
final Session session = sessionFactory.openSession();
|
||||
if (ManagedSessionContext.hasBind(sessionFactory)) {
|
||||
throw new IllegalStateException("Already in a unit of work!");
|
||||
@@ -25,11 +34,11 @@ public abstract class UnitOfWork<T> {
|
||||
ManagedSessionContext.bind(session);
|
||||
session.beginTransaction();
|
||||
try {
|
||||
t = runInSession();
|
||||
t = sessionRunner.runInSession();
|
||||
commitTransaction(session);
|
||||
} catch (Exception e) {
|
||||
rollbackTransaction(session);
|
||||
this.<RuntimeException> rethrow(e);
|
||||
UnitOfWork.<RuntimeException> rethrow(e);
|
||||
}
|
||||
} finally {
|
||||
session.close();
|
||||
@@ -38,14 +47,14 @@ public abstract class UnitOfWork<T> {
|
||||
return t;
|
||||
}
|
||||
|
||||
private void rollbackTransaction(Session session) {
|
||||
private static void rollbackTransaction(Session session) {
|
||||
final Transaction txn = session.getTransaction();
|
||||
if (txn != null && txn.isActive()) {
|
||||
txn.rollback();
|
||||
}
|
||||
}
|
||||
|
||||
private void commitTransaction(Session session) {
|
||||
private static void commitTransaction(Session session) {
|
||||
final Transaction txn = session.getTransaction();
|
||||
if (txn != null && txn.isActive()) {
|
||||
txn.commit();
|
||||
@@ -53,7 +62,7 @@ public abstract class UnitOfWork<T> {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <E extends Exception> void rethrow(Exception e) throws E {
|
||||
private static <E extends Exception> void rethrow(Exception e) throws E {
|
||||
throw (E) e;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import javax.inject.Singleton;
|
||||
import org.hibernate.SessionFactory;
|
||||
|
||||
import com.commafeed.backend.model.QUser;
|
||||
import com.commafeed.backend.model.QUserRole;
|
||||
import com.commafeed.backend.model.User;
|
||||
|
||||
@Singleton
|
||||
@@ -20,21 +19,18 @@ public class UserDAO extends GenericDAO<User> {
|
||||
}
|
||||
|
||||
public User findByName(String name) {
|
||||
return newQuery().from(user).where(user.name.equalsIgnoreCase(name)).leftJoin(user.roles, QUserRole.userRole).fetch()
|
||||
.uniqueResult(user);
|
||||
return query().selectFrom(user).where(user.name.equalsIgnoreCase(name)).fetchOne();
|
||||
}
|
||||
|
||||
public User findByApiKey(String key) {
|
||||
return newQuery().from(user).where(user.apiKey.equalsIgnoreCase(key)).leftJoin(user.roles, QUserRole.userRole).fetch()
|
||||
.uniqueResult(user);
|
||||
return query().selectFrom(user).where(user.apiKey.equalsIgnoreCase(key)).fetchOne();
|
||||
}
|
||||
|
||||
public User findByEmail(String email) {
|
||||
return newQuery().from(user).where(user.email.equalsIgnoreCase(email)).leftJoin(user.roles, QUserRole.userRole).fetch()
|
||||
.uniqueResult(user);
|
||||
return query().selectFrom(user).where(user.email.equalsIgnoreCase(email)).fetchOne();
|
||||
}
|
||||
|
||||
public long count() {
|
||||
return newQuery().from(user).count();
|
||||
return query().selectFrom(user).fetchCount();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.commafeed.backend.dao;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -12,7 +13,6 @@ import com.commafeed.backend.model.QUserRole;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.commafeed.backend.model.UserRole;
|
||||
import com.commafeed.backend.model.UserRole.Role;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
@Singleton
|
||||
public class UserRoleDAO extends GenericDAO<UserRole> {
|
||||
@@ -25,18 +25,14 @@ public class UserRoleDAO extends GenericDAO<UserRole> {
|
||||
}
|
||||
|
||||
public List<UserRole> findAll() {
|
||||
return newQuery().from(role).leftJoin(role.user).fetch().distinct().list(role);
|
||||
return query().selectFrom(role).leftJoin(role.user).fetchJoin().distinct().fetch();
|
||||
}
|
||||
|
||||
public List<UserRole> findAll(User user) {
|
||||
return newQuery().from(role).where(role.user.eq(user)).distinct().list(role);
|
||||
return query().selectFrom(role).where(role.user.eq(user)).distinct().fetch();
|
||||
}
|
||||
|
||||
public Set<Role> findRoles(User user) {
|
||||
Set<Role> list = Sets.newHashSet();
|
||||
for (UserRole role : findAll(user)) {
|
||||
list.add(role.getRole());
|
||||
}
|
||||
return list;
|
||||
return findAll(user).stream().map(r -> r.getRole()).collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,6 @@ public class UserSettingsDAO extends GenericDAO<UserSettings> {
|
||||
}
|
||||
|
||||
public UserSettings findByUser(User user) {
|
||||
return newQuery().from(settings).where(settings.user.eq(user)).uniqueResult(settings);
|
||||
return query().selectFrom(settings).where(settings.user.eq(user)).fetchFirst();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,11 @@ package com.commafeed.backend.favicon;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.commafeed.backend.model.Feed;
|
||||
|
||||
@@ -18,7 +20,7 @@ public abstract class AbstractFaviconFetcher {
|
||||
|
||||
protected static int TIMEOUT = 4000;
|
||||
|
||||
public abstract byte[] fetch(Feed feed);
|
||||
public abstract Favicon fetch(Feed feed);
|
||||
|
||||
protected boolean isValidIconResponse(byte[] content, String contentType) {
|
||||
if (content == null) {
|
||||
@@ -48,4 +50,11 @@ public abstract class AbstractFaviconFetcher {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public static class Favicon {
|
||||
private final byte[] icon;
|
||||
private final String mediaType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import javax.inject.Singleton;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.select.Elements;
|
||||
@@ -28,9 +28,15 @@ public class DefaultFaviconFetcher extends AbstractFaviconFetcher {
|
||||
private final HttpGetter getter;
|
||||
|
||||
@Override
|
||||
public byte[] fetch(Feed feed) {
|
||||
String url = feed.getLink() != null ? feed.getLink() : feed.getUrl();
|
||||
public Favicon fetch(Feed feed) {
|
||||
Favicon icon = fetch(feed.getLink());
|
||||
if (icon == null) {
|
||||
icon = fetch(feed.getUrl());
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
private Favicon fetch(String url) {
|
||||
if (url == null) {
|
||||
log.debug("url is null");
|
||||
return null;
|
||||
@@ -47,7 +53,7 @@ public class DefaultFaviconFetcher extends AbstractFaviconFetcher {
|
||||
url = url.substring(0, firstSlash);
|
||||
}
|
||||
|
||||
byte[] icon = getIconAtRoot(url);
|
||||
Favicon icon = getIconAtRoot(url);
|
||||
|
||||
if (icon == null) {
|
||||
icon = getIconInPage(url);
|
||||
@@ -56,7 +62,7 @@ public class DefaultFaviconFetcher extends AbstractFaviconFetcher {
|
||||
return icon;
|
||||
}
|
||||
|
||||
private byte[] getIconAtRoot(String url) {
|
||||
private Favicon getIconAtRoot(String url) {
|
||||
byte[] bytes = null;
|
||||
String contentType = null;
|
||||
|
||||
@@ -67,23 +73,25 @@ public class DefaultFaviconFetcher extends AbstractFaviconFetcher {
|
||||
bytes = result.getContent();
|
||||
contentType = result.getContentType();
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to retrieve iconAtRoot for url {}: ", url, e);
|
||||
log.debug("Failed to retrieve iconAtRoot for url {}: ", url);
|
||||
log.trace("Failed to retrieve iconAtRoot for url {}: ", url, e);
|
||||
}
|
||||
|
||||
if (!isValidIconResponse(bytes, contentType)) {
|
||||
bytes = null;
|
||||
return null;
|
||||
}
|
||||
return bytes;
|
||||
return new Favicon(bytes, contentType);
|
||||
}
|
||||
|
||||
private byte[] getIconInPage(String url) {
|
||||
private Favicon getIconInPage(String url) {
|
||||
|
||||
Document doc = null;
|
||||
try {
|
||||
HttpResult result = getter.getBinary(url, TIMEOUT);
|
||||
doc = Jsoup.parse(new String(result.getContent()), url);
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to retrieve page to find icon", e);
|
||||
log.debug("Failed to retrieve page to find icon");
|
||||
log.trace("Failed to retrieve page to find icon", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -109,7 +117,8 @@ public class DefaultFaviconFetcher extends AbstractFaviconFetcher {
|
||||
bytes = result.getContent();
|
||||
contentType = result.getContentType();
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to retrieve icon found in page {}", href, e);
|
||||
log.debug("Failed to retrieve icon found in page {}", href);
|
||||
log.trace("Failed to retrieve icon found in page {}", href, e);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -118,6 +127,6 @@ public class DefaultFaviconFetcher extends AbstractFaviconFetcher {
|
||||
return null;
|
||||
}
|
||||
|
||||
return bytes;
|
||||
return new Favicon(bytes, contentType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.commafeed.backend.favicon;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.utils.URLEncodedUtils;
|
||||
|
||||
import com.commafeed.backend.HttpGetter;
|
||||
import com.commafeed.backend.HttpGetter.HttpResult;
|
||||
import com.commafeed.backend.model.Feed;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
|
||||
@Singleton
|
||||
public class FacebookFaviconFetcher extends AbstractFaviconFetcher {
|
||||
|
||||
private final HttpGetter getter;
|
||||
|
||||
@Override
|
||||
public Favicon fetch(Feed feed) {
|
||||
String url = feed.getUrl();
|
||||
|
||||
if (!url.toLowerCase().contains("www.facebook.com")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String userName = extractUserName(url);
|
||||
if (userName == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String iconUrl = String.format("https://graph.facebook.com/%s/picture?type=square&height=16", userName);
|
||||
|
||||
byte[] bytes = null;
|
||||
String contentType = null;
|
||||
|
||||
try {
|
||||
log.debug("Getting Facebook user's icon, {}", url);
|
||||
|
||||
HttpResult iconResult = getter.getBinary(iconUrl, TIMEOUT);
|
||||
bytes = iconResult.getContent();
|
||||
contentType = iconResult.getContentType();
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to retrieve Facebook icon", e);
|
||||
}
|
||||
|
||||
if (!isValidIconResponse(bytes, contentType)) {
|
||||
return null;
|
||||
}
|
||||
return new Favicon(bytes, contentType);
|
||||
}
|
||||
|
||||
private String extractUserName(String url) {
|
||||
URI uri = null;
|
||||
try {
|
||||
uri = new URI(url);
|
||||
} catch (URISyntaxException e) {
|
||||
log.debug("could not parse url", e);
|
||||
return null;
|
||||
}
|
||||
List<NameValuePair> params = URLEncodedUtils.parse(uri, StandardCharsets.UTF_8.name());
|
||||
for (NameValuePair param : params) {
|
||||
if ("id".equals(param.getName())) {
|
||||
return param.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,66 +1,91 @@
|
||||
package com.commafeed.backend.favicon;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.select.Elements;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.utils.URLEncodedUtils;
|
||||
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.backend.HttpGetter;
|
||||
import com.commafeed.backend.HttpGetter.HttpResult;
|
||||
import com.commafeed.backend.model.Feed;
|
||||
import com.google.api.client.http.HttpRequest;
|
||||
import com.google.api.client.http.HttpRequestInitializer;
|
||||
import com.google.api.client.http.javanet.NetHttpTransport;
|
||||
import com.google.api.client.json.jackson2.JacksonFactory;
|
||||
import com.google.api.services.youtube.YouTube;
|
||||
import com.google.api.services.youtube.model.Channel;
|
||||
import com.google.api.services.youtube.model.ChannelListResponse;
|
||||
import com.google.api.services.youtube.model.Thumbnail;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
|
||||
@RequiredArgsConstructor(onConstructor = @__({ @Inject }) )
|
||||
@Singleton
|
||||
public class YoutubeFaviconFetcher extends AbstractFaviconFetcher {
|
||||
|
||||
private final HttpGetter getter;
|
||||
private final CommaFeedConfiguration config;
|
||||
|
||||
@Override
|
||||
public byte[] fetch(Feed feed) {
|
||||
public Favicon fetch(Feed feed) {
|
||||
String url = feed.getUrl();
|
||||
|
||||
if (!url.toLowerCase().contains("://gdata.youtube.com/")) {
|
||||
if (!url.toLowerCase().contains("youtube.com/feeds/videos.xml")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String userName = extractUserName(url);
|
||||
if (userName == null) {
|
||||
String googleAuthKey = config.getApplicationSettings().getGoogleAuthKey();
|
||||
if (googleAuthKey == null) {
|
||||
log.debug("no google auth key configured");
|
||||
return null;
|
||||
}
|
||||
|
||||
String profileUrl = "https://gdata.youtube.com/feeds/users/" + userName;
|
||||
|
||||
byte[] bytes = null;
|
||||
String contentType = null;
|
||||
|
||||
try {
|
||||
log.debug("Getting YouTube user's icon, {}", url);
|
||||
|
||||
// initial get to translate username to obscure user thumbnail URL
|
||||
HttpResult profileResult = getter.getBinary(profileUrl, TIMEOUT);
|
||||
Document doc = Jsoup.parse(new String(profileResult.getContent()), profileUrl);
|
||||
|
||||
Elements thumbnails = doc.select("media|thumbnail");
|
||||
if (thumbnails.isEmpty()) {
|
||||
List<NameValuePair> params = URLEncodedUtils.parse(url.substring(url.indexOf("?") + 1), StandardCharsets.UTF_8);
|
||||
Optional<NameValuePair> userId = params.stream().filter(nvp -> nvp.getName().equalsIgnoreCase("user")).findFirst();
|
||||
Optional<NameValuePair> channelId = params.stream().filter(nvp -> nvp.getName().equalsIgnoreCase("channel_id")).findFirst();
|
||||
if (!userId.isPresent() && !channelId.isPresent()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String thumbnailUrl = thumbnails.get(0).attr("abs:url");
|
||||
YouTube youtube = new YouTube.Builder(new NetHttpTransport(), JacksonFactory.getDefaultInstance(),
|
||||
new HttpRequestInitializer() {
|
||||
@Override
|
||||
public void initialize(HttpRequest request) throws IOException {
|
||||
}
|
||||
}).setApplicationName("CommaFeed").build();
|
||||
|
||||
int thumbnailStart = thumbnailUrl.indexOf("<media:thumbnail url='");
|
||||
int thumbnailEnd = thumbnailUrl.indexOf("'/>", thumbnailStart);
|
||||
if (thumbnailStart != -1) {
|
||||
thumbnailUrl = thumbnailUrl.substring(thumbnailStart + "<media:thumbnail url='".length(), thumbnailEnd);
|
||||
YouTube.Channels.List list = youtube.channels().list("snippet");
|
||||
list.setKey(googleAuthKey);
|
||||
if (userId.isPresent()) {
|
||||
list.setForUsername(userId.get().getValue());
|
||||
} else {
|
||||
list.setId(channelId.get().getValue());
|
||||
}
|
||||
|
||||
// final get to actually retrieve the thumbnail
|
||||
HttpResult iconResult = getter.getBinary(thumbnailUrl, TIMEOUT);
|
||||
log.debug("contacting youtube api");
|
||||
ChannelListResponse response = list.execute();
|
||||
if (response.getItems().isEmpty()) {
|
||||
log.debug("youtube api returned no items");
|
||||
return null;
|
||||
}
|
||||
|
||||
Channel channel = response.getItems().get(0);
|
||||
Thumbnail thumbnail = channel.getSnippet().getThumbnails().getDefault();
|
||||
|
||||
log.debug("fetching favicon");
|
||||
HttpResult iconResult = getter.getBinary(thumbnail.getUrl(), TIMEOUT);
|
||||
bytes = iconResult.getContent();
|
||||
contentType = iconResult.getContentType();
|
||||
} catch (Exception e) {
|
||||
@@ -68,23 +93,8 @@ public class YoutubeFaviconFetcher extends AbstractFaviconFetcher {
|
||||
}
|
||||
|
||||
if (!isValidIconResponse(bytes, contentType)) {
|
||||
bytes = null;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private String extractUserName(String url) {
|
||||
int apiOrBase = url.indexOf("/users/");
|
||||
if (apiOrBase == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int userEndSlash = url.indexOf('/', apiOrBase + "/users/".length());
|
||||
if (userEndSlash == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return url.substring(apiOrBase + "/users/".length(), userEndSlash);
|
||||
return new Favicon(bytes, contentType);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.commafeed.backend.feed;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* A keyword used in a search query
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public class FeedEntryKeyword {
|
||||
|
||||
public static enum Mode {
|
||||
INCLUDE, EXCLUDE;
|
||||
}
|
||||
|
||||
private final String keyword;
|
||||
private final Mode mode;
|
||||
|
||||
public static List<FeedEntryKeyword> fromQueryString(String keywords) {
|
||||
List<FeedEntryKeyword> list = new ArrayList<>();
|
||||
if (keywords != null) {
|
||||
for (String keyword : StringUtils.split(keywords)) {
|
||||
boolean not = false;
|
||||
if (keyword.startsWith("-") || keyword.startsWith("!")) {
|
||||
not = true;
|
||||
keyword = keyword.substring(1);
|
||||
}
|
||||
list.add(new FeedEntryKeyword(keyword, not ? Mode.EXCLUDE : Mode.INCLUDE));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -41,16 +41,16 @@ public class FeedFetcher {
|
||||
byte[] content = result.getContent();
|
||||
|
||||
try {
|
||||
fetchedFeed = parser.parse(feedUrl, content);
|
||||
fetchedFeed = parser.parse(result.getUrlAfterRedirect(), content);
|
||||
} catch (FeedException e) {
|
||||
if (extractFeedUrlFromHtml) {
|
||||
String extractedUrl = extractFeedUrl(StringUtils.newStringUtf8(result.getContent()), feedUrl);
|
||||
if (org.apache.commons.lang.StringUtils.isNotBlank(extractedUrl)) {
|
||||
if (org.apache.commons.lang3.StringUtils.isNotBlank(extractedUrl)) {
|
||||
feedUrl = extractedUrl;
|
||||
|
||||
result = getter.getBinary(extractedUrl, lastModified, eTag, timeout);
|
||||
content = result.getContent();
|
||||
fetchedFeed = parser.parse(feedUrl, content);
|
||||
fetchedFeed = parser.parse(result.getUrlAfterRedirect(), content);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package com.commafeed.backend.feed;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.text.DateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -11,8 +13,7 @@ import javax.inject.Singleton;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang.SystemUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jdom2.Element;
|
||||
import org.jdom2.Namespace;
|
||||
import org.xml.sax.InputSource;
|
||||
@@ -20,10 +21,7 @@ import org.xml.sax.InputSource;
|
||||
import com.commafeed.backend.model.Feed;
|
||||
import com.commafeed.backend.model.FeedEntry;
|
||||
import com.commafeed.backend.model.FeedEntryContent;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.rometools.rome.feed.synd.SyndContent;
|
||||
import com.rometools.rome.feed.synd.SyndEnclosure;
|
||||
import com.rometools.rome.feed.synd.SyndEntry;
|
||||
import com.rometools.rome.feed.synd.SyndFeed;
|
||||
@@ -43,20 +41,13 @@ public class FeedParser {
|
||||
private static final Date START = new Date(86400000);
|
||||
private static final Date END = new Date(1000l * Integer.MAX_VALUE - 86400000);
|
||||
|
||||
private static final Function<SyndContent, String> CONTENT_TO_STRING = new Function<SyndContent, String>() {
|
||||
@Override
|
||||
public String apply(SyndContent content) {
|
||||
return content.getValue();
|
||||
}
|
||||
};
|
||||
|
||||
public FetchedFeed parse(String feedUrl, byte[] xml) throws FeedException {
|
||||
FetchedFeed fetchedFeed = new FetchedFeed();
|
||||
Feed feed = fetchedFeed.getFeed();
|
||||
List<FeedEntry> entries = fetchedFeed.getEntries();
|
||||
|
||||
try {
|
||||
String encoding = FeedUtils.guessEncoding(xml);
|
||||
Charset encoding = FeedUtils.guessEncoding(xml);
|
||||
String xmlString = FeedUtils.trimInvalidXmlCharacters(new String(xml, encoding));
|
||||
if (xmlString == null) {
|
||||
throw new FeedException("Input string is null for url " + feedUrl);
|
||||
@@ -86,7 +77,7 @@ public class FeedParser {
|
||||
}
|
||||
entry.setGuid(FeedUtils.truncate(guid, 2048));
|
||||
entry.setUpdated(validateDate(getEntryUpdateDate(item), true));
|
||||
entry.setUrl(FeedUtils.truncate(FeedUtils.toAbsoluteUrl(item.getLink(), feed.getLink(), feed.getUrlAfterRedirect()), 2048));
|
||||
entry.setUrl(FeedUtils.truncate(FeedUtils.toAbsoluteUrl(item.getLink(), feed.getLink(), feedUrl), 2048));
|
||||
|
||||
// if link is empty but guid is used as url
|
||||
if (StringUtils.isBlank(entry.getUrl()) && StringUtils.startsWith(entry.getGuid(), "http")) {
|
||||
@@ -95,6 +86,8 @@ public class FeedParser {
|
||||
|
||||
FeedEntryContent content = new FeedEntryContent();
|
||||
content.setContent(getContent(item));
|
||||
content.setCategories(FeedUtils.truncate(
|
||||
item.getCategories().stream().map(c -> c.getName()).collect(Collectors.joining(", ")), 4096));
|
||||
content.setTitle(getTitle(item));
|
||||
content.setAuthor(StringUtils.trimToNull(item.getAuthor()));
|
||||
SyndEnclosure enclosure = Iterables.getFirst(item.getEnclosures(), null);
|
||||
@@ -173,7 +166,7 @@ public class FeedParser {
|
||||
if (item.getContents().isEmpty()) {
|
||||
content = item.getDescription() == null ? null : item.getDescription().getValue();
|
||||
} else {
|
||||
content = StringUtils.join(Collections2.transform(item.getContents(), CONTENT_TO_STRING), SystemUtils.LINE_SEPARATOR);
|
||||
content = item.getContents().stream().map(c -> c.getValue()).collect(Collectors.joining(System.lineSeparator()));
|
||||
}
|
||||
return StringUtils.trimToNull(content);
|
||||
}
|
||||
|
||||
@@ -1,40 +1,45 @@
|
||||
package com.commafeed.backend.feed;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.lang.time.DateUtils;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import org.hibernate.SessionFactory;
|
||||
|
||||
import com.codahale.metrics.Gauge;
|
||||
import com.codahale.metrics.Meter;
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.backend.dao.FeedDAO;
|
||||
import com.commafeed.backend.dao.UnitOfWork;
|
||||
import com.commafeed.backend.model.Feed;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Queues;
|
||||
|
||||
@Singleton
|
||||
public class FeedQueues {
|
||||
|
||||
private SessionFactory sessionFactory;
|
||||
private final FeedDAO feedDAO;
|
||||
private final CommaFeedConfiguration config;
|
||||
|
||||
private Queue<FeedRefreshContext> addQueue = Queues.newConcurrentLinkedQueue();
|
||||
private Queue<FeedRefreshContext> takeQueue = Queues.newConcurrentLinkedQueue();
|
||||
private Queue<Feed> giveBackQueue = Queues.newConcurrentLinkedQueue();
|
||||
private Queue<FeedRefreshContext> addQueue = new ConcurrentLinkedQueue<>();
|
||||
private Queue<FeedRefreshContext> takeQueue = new ConcurrentLinkedQueue<>();
|
||||
private Queue<Feed> giveBackQueue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
private Meter refill;
|
||||
|
||||
@Inject
|
||||
public FeedQueues(FeedDAO feedDAO, CommaFeedConfiguration config, MetricRegistry metrics) {
|
||||
public FeedQueues(SessionFactory sessionFactory, FeedDAO feedDAO, CommaFeedConfiguration config, MetricRegistry metrics) {
|
||||
this.sessionFactory = sessionFactory;
|
||||
this.config = config;
|
||||
this.feedDAO = feedDAO;
|
||||
|
||||
@@ -78,13 +83,7 @@ public class FeedQueues {
|
||||
public void add(Feed feed, boolean urgent) {
|
||||
int refreshInterval = config.getApplicationSettings().getRefreshIntervalMinutes();
|
||||
if (feed.getLastUpdated() == null || feed.getLastUpdated().before(DateUtils.addMinutes(new Date(), -1 * refreshInterval))) {
|
||||
boolean alreadyQueued = false;
|
||||
for (FeedRefreshContext context : addQueue) {
|
||||
if (context.getFeed().getId().equals(feed.getId())) {
|
||||
alreadyQueued = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
boolean alreadyQueued = addQueue.stream().anyMatch(c -> c.getFeed().getId().equals(feed.getId()));
|
||||
if (!alreadyQueued) {
|
||||
addQueue.add(new FeedRefreshContext(feed, urgent));
|
||||
}
|
||||
@@ -97,7 +96,7 @@ public class FeedQueues {
|
||||
private void refill() {
|
||||
refill.mark();
|
||||
|
||||
List<FeedRefreshContext> contexts = Lists.newArrayList();
|
||||
List<FeedRefreshContext> contexts = new ArrayList<>();
|
||||
int batchSize = Math.min(100, 3 * config.getApplicationSettings().getBackgroundThreads());
|
||||
|
||||
// add feeds we got from the add() method
|
||||
@@ -109,7 +108,7 @@ public class FeedQueues {
|
||||
// add feeds that are up to refresh from the database
|
||||
int count = batchSize - contexts.size();
|
||||
if (count > 0) {
|
||||
List<Feed> feeds = feedDAO.findNextUpdatable(count, getLastLoginThreshold());
|
||||
List<Feed> feeds = UnitOfWork.call(sessionFactory, () -> feedDAO.findNextUpdatable(count, getLastLoginThreshold()));
|
||||
for (Feed feed : feeds) {
|
||||
contexts.add(new FeedRefreshContext(feed, false));
|
||||
}
|
||||
@@ -117,7 +116,7 @@ public class FeedQueues {
|
||||
|
||||
// set the disabledDate as we use it in feedDAO to decide what to refresh next. We also use a map to remove
|
||||
// duplicates.
|
||||
Map<Long, FeedRefreshContext> map = Maps.newLinkedHashMap();
|
||||
Map<Long, FeedRefreshContext> map = new LinkedHashMap<>();
|
||||
for (FeedRefreshContext context : contexts) {
|
||||
Feed feed = context.getFeed();
|
||||
feed.setDisabledUntil(DateUtils.addMinutes(new Date(), config.getApplicationSettings().getRefreshIntervalMinutes()));
|
||||
@@ -135,11 +134,8 @@ public class FeedQueues {
|
||||
}
|
||||
|
||||
// update all feeds in the database
|
||||
List<Feed> feeds = Lists.newArrayList();
|
||||
for (FeedRefreshContext context : map.values()) {
|
||||
feeds.add(context.getFeed());
|
||||
}
|
||||
feedDAO.merge(feeds);
|
||||
List<Feed> feeds = map.values().stream().map(c -> c.getFeed()).collect(Collectors.toList());
|
||||
UnitOfWork.run(sessionFactory, () -> feedDAO.saveOrUpdate(feeds));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,7 +150,7 @@ public class FeedQueues {
|
||||
}
|
||||
|
||||
private Date getLastLoginThreshold() {
|
||||
if (config.getApplicationSettings().isHeavyLoad()) {
|
||||
if (config.getApplicationSettings().getHeavyLoad()) {
|
||||
return DateUtils.addDays(new Date(), -30);
|
||||
} else {
|
||||
return null;
|
||||
|
||||
@@ -2,9 +2,14 @@ package com.commafeed.backend.feed;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import com.commafeed.backend.model.Feed;
|
||||
import com.commafeed.backend.model.FeedEntry;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class FeedRefreshContext {
|
||||
private Feed feed;
|
||||
private List<FeedEntry> entries;
|
||||
@@ -14,29 +19,4 @@ public class FeedRefreshContext {
|
||||
this.feed = feed;
|
||||
this.urgent = isUrgent;
|
||||
}
|
||||
|
||||
public Feed getFeed() {
|
||||
return feed;
|
||||
}
|
||||
|
||||
public void setFeed(Feed feed) {
|
||||
this.feed = feed;
|
||||
}
|
||||
|
||||
public boolean isUrgent() {
|
||||
return urgent;
|
||||
}
|
||||
|
||||
public void setUrgent(boolean urgent) {
|
||||
this.urgent = urgent;
|
||||
}
|
||||
|
||||
public List<FeedEntry> getEntries() {
|
||||
return entries;
|
||||
}
|
||||
|
||||
public void setEntries(List<FeedEntry> entries) {
|
||||
this.entries = entries;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ import java.util.concurrent.RejectedExecutionHandler;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import com.codahale.metrics.Gauge;
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Wraps a {@link ThreadPoolExecutor} instance. Blocks when queue is full instead of rejecting the task. Allow priority queueing by using
|
||||
* {@link Task} instead of {@link Runnable}
|
||||
@@ -37,7 +37,14 @@ public class FeedRefreshExecutor {
|
||||
return offerLast(r);
|
||||
}
|
||||
}
|
||||
});
|
||||
}) {
|
||||
@Override
|
||||
protected void afterExecute(Runnable r, Throwable t) {
|
||||
if (t != null) {
|
||||
log.error("thread from pool {} threw a runtime exception", poolName, t);
|
||||
}
|
||||
}
|
||||
};
|
||||
pool.setRejectedExecutionHandler(new RejectedExecutionHandler() {
|
||||
@Override
|
||||
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
|
||||
|
||||
@@ -10,13 +10,10 @@ import javax.inject.Singleton;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.hibernate.SessionFactory;
|
||||
|
||||
import com.codahale.metrics.Meter;
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.backend.dao.FeedDAO;
|
||||
import com.commafeed.backend.dao.UnitOfWork;
|
||||
|
||||
/**
|
||||
* Infinite loop fetching feeds from @FeedQueues and queuing them to the {@link FeedRefreshWorker} pool.
|
||||
@@ -26,7 +23,6 @@ import com.commafeed.backend.dao.UnitOfWork;
|
||||
@Singleton
|
||||
public class FeedRefreshTaskGiver implements Managed {
|
||||
|
||||
private final SessionFactory sessionFactory;
|
||||
private final FeedQueues queues;
|
||||
private final FeedRefreshWorker worker;
|
||||
|
||||
@@ -36,9 +32,8 @@ public class FeedRefreshTaskGiver implements Managed {
|
||||
private Meter threadWaited;
|
||||
|
||||
@Inject
|
||||
public FeedRefreshTaskGiver(SessionFactory sessionFactory, FeedQueues queues, FeedDAO feedDAO, FeedRefreshWorker worker,
|
||||
CommaFeedConfiguration config, MetricRegistry metrics) {
|
||||
this.sessionFactory = sessionFactory;
|
||||
public FeedRefreshTaskGiver(FeedQueues queues, FeedDAO feedDAO, FeedRefreshWorker worker, CommaFeedConfiguration config,
|
||||
MetricRegistry metrics) {
|
||||
this.queues = queues;
|
||||
this.worker = worker;
|
||||
|
||||
@@ -68,12 +63,7 @@ public class FeedRefreshTaskGiver implements Managed {
|
||||
public void run() {
|
||||
while (!executor.isShutdown()) {
|
||||
try {
|
||||
FeedRefreshContext context = new UnitOfWork<FeedRefreshContext>(sessionFactory) {
|
||||
@Override
|
||||
protected FeedRefreshContext runInSession() throws Exception {
|
||||
return queues.take();
|
||||
}
|
||||
}.run();
|
||||
FeedRefreshContext context = queues.take();
|
||||
if (context != null) {
|
||||
feedRefreshed.mark();
|
||||
worker.updateFeed(context);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user