Compare commits

...

215 Commits
1.3.0 ... 1.5.0

Author SHA1 Message Date
Athou
bbcd79e49f Merge pull request #602 from swoga/patch-1
Added translations for german
2014-07-11 12:38:55 +02:00
Peter
4dabf47822 Update de.properties 2014-07-09 15:46:04 +02:00
Athou
db258d4ecc starred items ignore the unreadOnly flag 2014-05-07 23:27:41 +02:00
Athou
8b237db690 ignore loading bar for some requests 2014-04-29 15:55:03 +02:00
Athou
416350c004 reset latency threshold to default 2014-04-29 15:46:44 +02:00
Athou
8d63377e78 patch angular loading bar to allow undefined config 2014-04-29 13:14:54 +02:00
Athou
377176df05 don't minimize already minified files 2014-04-29 12:43:35 +02:00
Athou
95da0078b3 angularjs upgrade 2014-04-29 12:16:02 +02:00
Athou
6392b87afc upgrade to 0.4.0 2014-04-29 12:02:39 +02:00
Athou
ba04d2adfe use angular-loading-bar instead of spin.js for ajax indicator 2014-04-29 10:44:39 +02:00
Athou
517ce1a726 dependencies update 2014-04-22 16:56:31 +02:00
Athou
36492cbff5 fix opml export 2014-04-21 09:53:04 +02:00
Athou
4b46aa08ac display title texts for images on mobile (fix #585) 2014-04-21 09:47:26 +02:00
Athou
1a9a80c0da change title pattern (fix #584) 2014-04-19 06:31:41 +02:00
Athou
32a30019a7 individual settings for share buttons, and added tumblr (#582) 2014-04-16 12:30:25 +02:00
Athou
bb72131354 remove invalid content-encoding headers (fix #580) 2014-04-16 11:42:39 +02:00
Athou
3a8d72cab4 default charset for http should be iso-8859-1 (https://www.w3.org/International/O-HTTP-charset) 2014-04-09 16:21:33 +02:00
Athou
f5f7a8e63b remove invalid content encoding headers (#580) 2014-04-04 11:41:52 +02:00
Athou
570c4f3a1f display cause of invalid feed (#580) 2014-04-04 11:41:15 +02:00
Athou
172164b74b Merge pull request #574 from ekovi/patch-17
Update _svetla.scss
2014-03-22 11:46:49 +01:00
ekovi
49835ae234 Update _svetla.scss 2014-03-22 11:31:31 +01:00
Athou
c4f1e910f8 Merge pull request #573 from ekovi/patch-16
Update _svetla.scss
2014-03-21 11:44:57 +01:00
ekovi
3a621b61c6 Update _svetla.scss
hopefully finished
2014-03-21 11:44:26 +01:00
Athou
c28f0d6788 changed rest endpoint to reflect cleanup task changes 2014-03-21 11:34:29 +01:00
Athou
2db9224ffc first clean entries then clean feeds 2014-03-21 11:28:51 +01:00
Athou
043b1df585 more logging 2014-03-21 00:26:32 +01:00
Athou
0626200787 Merge pull request #572 from ekovi/patch-15
Update _svetla.scss
2014-03-20 23:39:21 +01:00
ekovi
b7ee61a8df Update _svetla.scss 2014-03-20 23:05:18 +01:00
Athou
6e1cdaf50e Merge pull request #571 from ekovi/patch-14
Update _dark.scss
2014-03-19 03:32:22 +01:00
ekovi
e770f802e7 Update _dark.scss 2014-03-18 21:39:33 +01:00
Athou
8e4cf77fcb Merge pull request #569 from ekovi/patch-12
added theme entry
2014-03-18 15:56:45 +01:00
Athou
bc3bd42ce3 Merge pull request #568 from ekovi/patch-11
new theme
2014-03-18 15:56:24 +01:00
Athou
f73e0ba307 Merge pull request #570 from ekovi/patch-13
added reference for new theme
2014-03-18 15:55:33 +01:00
ekovi
5703b5e8d4 added reference for new theme 2014-03-18 15:23:06 +01:00
ekovi
cecbb2cf72 added theme entry 2014-03-18 15:21:20 +01:00
ekovi
8638e4751d new theme 2014-03-18 15:18:19 +01:00
Athou
3b69e3b029 actually remove entries for feeds 2014-03-17 06:18:36 +01:00
Athou
dced21c8e4 revert ng-grid back to 2.0.7, fix admin user list 2014-03-16 15:17:23 +01:00
Athou
dab26af294 allow feeds without entries (fix #565) 2014-03-15 04:24:40 +01:00
Athou
65f118e561 Merge pull request #564 from ekovi/patch-9
Update _svetla.scss
2014-03-14 12:45:31 +01:00
ekovi
67f533b9f6 Update _svetla.scss
'popup-able' feedback button
2014-03-14 12:44:39 +01:00
Athou
93573bcdb7 Merge pull request #563 from ekovi/patch-8
Update _dark.scss
2014-03-14 12:44:12 +01:00
ekovi
2263801c55 Update _dark.scss
{}
2014-03-14 12:43:38 +01:00
Athou
10c34d0440 Merge pull request #562 from ekovi/patch-7
Update _dark.scss
2014-03-14 12:41:14 +01:00
ekovi
4430ef3847 Update _dark.scss
just a few minor changes (to better ofc)
2014-03-14 12:40:27 +01:00
Athou
8e331b908d Merge pull request #561 from ekovi/patch-6
Update _svetla.scss
2014-03-13 14:17:50 +01:00
ekovi
dbc6fb58e0 Update _svetla.scss 2014-03-13 14:03:38 +01:00
Athou
db298ab684 Merge pull request #560 from ekovi/patch-6
Update _dark.scss
2014-03-13 09:42:56 +01:00
ekovi
170a6095e6 Update _dark.scss 2014-03-13 09:30:28 +01:00
Athou
6dd1bf3281 restore pointer mouse icon on hover 2014-03-10 14:51:59 +01:00
Athou
b1500cebfd remove bold from labels 2014-03-10 14:37:45 +01:00
Athou
6202bdbc28 added default bootstrap theme 2014-03-10 14:37:45 +01:00
Athou
39bfb61b95 dependencies update 2014-03-10 14:37:45 +01:00
Athou
fa79524ed4 fix checkbox position 2014-03-10 14:37:44 +01:00
Athou
ab5b70e52b Merge pull request #558 from ekovi/patch-6
Update _dark.scss
2014-03-10 13:13:24 +01:00
ekovi
4f8cd53b83 Update _dark.scss
cleaned style and reworked a bit
2014-03-10 13:07:23 +01:00
Athou
afb6221e5e Merge pull request #557 from ekovi/patch-5
Update _dark.scss
2014-03-10 06:48:47 +01:00
ekovi
f78aedc30d Update _dark.scss
some minor stuff, transition, etc.
2014-03-10 00:25:20 +01:00
ekovi
80ff2c8ff7 Update _dark.scss 2014-03-09 20:40:42 +01:00
Athou
579a77dfc9 remove debug logging 2014-03-06 15:50:57 +01:00
Athou
f902d967a6 wording 2014-03-06 15:48:03 +01:00
Athou
0899e0b0bf server now returns wether the 'unread only' flag was ignored while generating the response (fix scrolling for results in a feed search) 2014-03-06 15:46:19 +01:00
Athou
65d6f8616b fix tag removal 2014-03-03 13:15:37 +01:00
Athou
5c27f0834c wait in the new spawned thread 2014-03-03 12:50:32 +01:00
Athou
a5f7b56bf2 let everything settle a little while longer 2014-03-03 12:42:16 +01:00
Athou
63ec92038c fix tagging 2014-03-03 12:03:42 +01:00
Athou
464ac36ddb added bootstrap webjar 2014-03-03 11:39:09 +01:00
Athou
840bc2ef7a added catalan language 2014-03-02 09:28:24 +01:00
Athou
e248504528 new webjars 2014-03-01 18:34:10 +01:00
Athou
f4f3d9ca48 handle invalid feeds having unescaped html entities 2014-03-01 18:19:49 +01:00
Athou
e727ee414b use webjars if possible 2014-02-28 14:45:12 +01:00
Athou
1e9295b386 wro4j upgrade 2014-02-28 13:43:30 +01:00
Athou
b980cdc2c2 Merge pull request #554 from ekovi/patch-4
Update controllers.js
2014-02-27 16:16:31 +01:00
Athou
fbe722facd Merge pull request #553 from ekovi/patch-3
Update app.scss
2014-02-27 16:16:29 +01:00
Athou
1897d8e0c0 Merge pull request #552 from ekovi/patch-2
_dark.scss
2014-02-27 16:16:27 +01:00
ekovi
3745a152aa Update controllers.js
added theme entry
2014-02-26 21:15:53 +01:00
ekovi
a7731acb08 Update app.scss
added theme entry
2014-02-26 21:11:56 +01:00
ekovi
16dd5deed4 update
forgot to enclose in #theme {}
2014-02-26 21:09:24 +01:00
ekovi
c9f70650a0 Create _dark.scss
new theme
2014-02-26 21:07:31 +01:00
Athou
eaa84253df dependencies update 2014-02-26 16:14:11 +01:00
Athou
45abcd7385 instantiate whitelist only once 2014-02-26 08:37:00 +01:00
Athou
8a633aa648 if link is empty, use guid instead if able (fix #551) 2014-02-26 08:36:40 +01:00
Athou
05e092062d wicket upgrade 2014-02-26 08:36:15 +01:00
Athou
e83602a05c load angular main js file first 2014-02-25 06:29:17 +01:00
Athou
abf8666e24 angularjs update 2014-02-25 06:15:45 +01:00
Athou
af1ccc6669 1.5.0-snapshot 2014-02-24 16:04:41 +01:00
Athou
cdcbfbff68 stable enough, time to tag 2014-02-24 16:03:38 +01:00
Athou
6860940afc fix javax.net.ssl.SSLProtocolException: handshake alert:
unrecognized_name (fix #549)
2014-02-22 13:12:55 +01:00
Athou
bfc2ee3663 readme update 2014-02-20 11:22:42 +01:00
Athou
b104622081 switch to bonecp 2014-02-20 10:32:16 +01:00
Athou
a861387bd7 handle null categories 2014-02-14 15:41:17 +01:00
Athou
b0f2260fad dependencies update 2014-02-12 21:02:00 +01:00
Athou
97f0d98ffd Merge pull request #544 from ebraminio/master
Update Persian translation
2014-02-03 04:23:11 -08:00
Ebrahim Byagowi
1ad58a029c Update Persian translation 2014-02-03 15:49:09 +03:30
Athou
4c27da0433 propagate exception 2014-02-02 12:30:41 +01:00
Athou
faf69b43c3 fix aspect ratio for large images 2014-02-02 12:19:52 +01:00
Athou
7fff561268 switch to dbcp as tomcat-pool seems to leak connections 2014-01-09 09:13:30 +01:00
Athou
5e1360a65b smarter log cleanup script (#533) 2014-01-07 12:02:15 +01:00
Athou
cc92d2f546 Merge pull request #537 from Busimus/patch-3
Fixed typo.
2014-01-07 02:53:14 -08:00
Athou
def75a250f Merge pull request #538 from Busimus/patch-6
Updated ru.properties
2014-01-07 02:52:53 -08:00
Alexander Bus
15cd7caf9b Update ru.properties 2014-01-06 23:05:41 +07:00
Alexander Bus
41a51530ef Fixed typo. 2014-01-06 22:14:13 +07:00
Athou
3a101941b3 mark as read when swiping entry title to the right 2013-12-18 18:36:22 +01:00
Athou
0976fee4df fix left padding on mobile 2013-12-13 17:42:40 +01:00
Athou
f87da777da improved support for fxos 2013-12-13 17:29:48 +01:00
Athou
e1c2bf0890 Merge pull request #534 from JKakku/patch-3
Translated confirmation messages
2013-12-12 22:46:25 -08:00
JKakku
b829defb30 Translated confirmation messages 2013-12-13 01:12:41 +02:00
Athou
fa8770d2a7 restore main content padding 2013-12-12 15:12:43 +01:00
Athou
222c8a65af git plugin update 2013-12-12 13:11:07 +01:00
Athou
76f5b67ac4 openshift changes (fix #532) 2013-12-12 11:51:48 +01:00
Athou
1791d49efe resizeable subscription list 2013-12-12 11:51:48 +01:00
Athou
64e1b5df09 fix sync during development 2013-12-12 10:25:08 +01:00
Athou
e1ff077623 Merge pull request #531 from LpSamuelm/patch-20
Translated new labels to Swedish
2013-12-11 01:24:42 -08:00
LpSamuelm
1361072558 Translated new labels to Swedish
Translatin' labels! Oh yeah!
2013-12-11 10:23:20 +01:00
Athou
5119434d21 more metrics 2013-12-10 14:02:06 +01:00
Athou
b29540b14e first add feeds from the queue, then if needed fetch feeds from the database to fill the batch 2013-12-10 11:08:52 +01:00
Athou
e69785bb89 smaller margins on mobile 2013-12-10 09:28:58 +01:00
Athou
76465fee07 remove horizontal scrolling on mobile 2013-12-06 04:12:19 +01:00
Athou
b52c459ebb calculate offset correctly for tags and starred listing (fix #530) 2013-12-05 12:25:17 +01:00
Athou
1d73982545 apply where clause when predicate list has been populated 2013-12-05 12:23:59 +01:00
Athou
74f6c45f36 Merge pull request #527 from evenorbert/patch-2
Update hu.properties
2013-12-02 07:20:56 -08:00
Norbert Evenich
0490b528e4 Update hu.properties
Updated hungarian translation.
2013-12-02 16:07:26 +01:00
Athou
ffa1e14449 feed url autofocus 2013-11-29 16:13:26 +01:00
Athou
b8fe89b2f4 tomee upgrade 2013-11-29 16:10:43 +01:00
Athou
94b293202c support for meego devices 2013-11-29 12:19:55 +01:00
Athou
7ef143a642 mousetrap upgrade 2013-11-29 11:24:35 +01:00
Athou
057f6916e9 spinjs upgrade 2013-11-29 11:24:29 +01:00
Athou
e24e892cb3 jquery upgrade 2013-11-29 11:22:03 +01:00
Athou
78976b06e2 lodash upgrade 2013-11-29 11:21:42 +01:00
Athou
96cfcd5b2b angularjs upgrade 2013-11-29 10:15:29 +01:00
Athou
12bda0122c make images fit available width 2013-11-28 19:35:31 +01:00
Athou
4ac4e5abf2 commons collection upgrade to 4.0 2013-11-28 16:56:52 +01:00
Athou
268f0f53a8 close tree when button clicked on mobile 2013-11-28 11:58:53 +01:00
Athou
71521f3428 fix mobile layout 2013-11-28 10:45:10 +01:00
Athou
6101fb2bef new translations 2013-11-28 10:12:52 +01:00
Athou
8f6aa0896b fix bootstrap dialogs 2013-11-28 10:12:10 +01:00
Athou
b8f0af5b2e fix radio buttons 2013-11-28 09:15:14 +01:00
Athou
32730f6c41 force full refreshes only when under heavy load 2013-11-27 15:54:23 +01:00
Athou
7caa99f8f2 bootstrap3 2013-11-27 15:54:23 +01:00
Athou
4f8e2ab478 limit transaction size 2013-11-27 08:07:44 +01:00
Athou
5c44f392ca remove warning 2013-11-26 15:11:44 +01:00
Athou
174d21fd4e move logic to user service 2013-11-26 15:09:32 +01:00
Athou
c2ed6d47f1 force a full refresh of the user's feeds when he logs in 2013-11-26 07:05:59 +01:00
Athou
0f6f717d09 tweaking batch size again 2013-11-20 11:33:21 +01:00
Athou
d7fb637f68 same batch size for all operations 2013-11-16 11:27:48 +01:00
Athou
fce9086b27 remove deprecated duplicate feed detection 2013-11-16 07:40:44 +01:00
Athou
97586cd2c8 batch delete entries too 2013-11-16 07:30:01 +01:00
Athou
b74458f0b0 more logging 2013-11-15 21:38:11 +01:00
Athou
7c7a0fceaf reduce batch size for feeds 2013-11-15 15:53:22 +01:00
Athou
425a8880cd smaller preview image 2013-11-14 15:27:31 +01:00
Athou
23fe90ec64 fix log message 2013-11-14 14:41:41 +01:00
Athou
c01ec5d039 fix openshift log cleanup script 2013-11-14 13:02:24 +01:00
Athou
4f284165c2 more database cleanup tasks 2013-11-14 12:56:08 +01:00
Athou
2a62ccff11 jackson upgrade 2013-11-14 12:42:07 +01:00
Athou
d09cf472dd disable services not needed 2013-11-13 16:26:23 +01:00
Athou
5c721ae6f5 prevent scanning classes twice 2013-11-13 16:18:34 +01:00
Athou
2bb8fcdb5f scan our classes only 2013-11-13 16:17:10 +01:00
Athou
6eda93098b trust should be the last filter 2013-11-12 15:52:30 +01:00
Athou
6344f554d6 rewrite iframes to use https if commafeed uses https 2013-11-12 15:44:56 +01:00
Athou
7e4c1f374c use latest wro4j in prod profile, using latest jruby version (fix #315) 2013-11-12 11:49:11 +01:00
Athou
28eaab7f7d trust enclosure urls 2013-11-12 11:35:22 +01:00
Athou
1937944f7e fix search 2013-11-12 09:57:59 +01:00
Athou
3b4b84fdab formatting 2013-11-12 09:45:26 +01:00
Athou
32325bb49c angularjs 1.2.0 upgrade 2013-11-12 09:43:42 +01:00
Athou
c01c1e93f9 lombok upgrade 2013-11-12 08:53:38 +01:00
Athou
eac096019f jsoup upgrade 2013-11-12 08:53:22 +01:00
Athou
9f9389e846 liquibase upgrade 2013-11-12 08:53:05 +01:00
Athou
a71317881f wicket upgrade 2013-11-12 08:52:46 +01:00
Athou
7092824c96 grid dates formatting (fix #421) 2013-11-08 11:37:45 +01:00
Athou
0ff998bbd7 bigger items on mobile 2013-11-08 11:37:45 +01:00
Athou
fc318ad211 smarter mobile detection (fix #255 and fix #487) 2013-11-08 11:37:44 +01:00
Athou
73323335cb cosmetic fix 2013-11-08 11:37:44 +01:00
Athou
ef57c5523d Merge pull request #523 from utimukat55/translate_ja
Add translation ja
2013-11-07 06:10:46 -08:00
utimukat55
846f4a7222 Add translation ja 2013-11-07 21:27:14 +09:00
Athou
05036778d6 httpclient upgrade 2013-11-05 15:27:19 +01:00
Athou
52df661238 mysql jdbc driver update 2013-10-29 13:26:39 +01:00
Athou
7957dc237e for generated feeds, set 'all' as default instead of 'unread' 2013-10-29 07:05:19 +01:00
Athou
3fe419ba2f disable openejb stats 2013-10-24 14:50:51 +02:00
Athou
61944656b8 allow same query parameters for entriesAsFeed (fix #521) 2013-10-24 09:50:35 +02:00
Athou
1cb997b66d moved query 2013-10-24 09:50:35 +02:00
Athou
89463808db return exception stacktrace in the body 2013-10-23 07:55:14 +02:00
Athou
6aca66d8cf prevent NPE 2013-10-20 17:12:53 +02:00
Athou
38f8102fb3 readability support (fix #108) 2013-10-20 15:26:58 +02:00
Athou
e709499240 Merge pull request #520 from LpSamuelm/patch-19
Translated new labels to Swedish
2013-10-15 18:34:45 -07:00
LpSamuelm
0b714d5e52 Translated new labels to Swedish
WOAH TAGS
2013-10-16 03:32:12 +02:00
Athou
98e4f0c6dc Merge pull request #519 from ekovi/patch-1
updated
2013-10-15 11:18:32 -07:00
ekovi
d82d0af565 updated 2013-10-15 19:21:00 +02:00
Athou
d8abb7039d Merge pull request #518 from JKakku/patch-2
Update fi.properties
2013-10-15 09:22:26 -07:00
JKakku
84dc11048d Update fi.properties
Added the missing stuff since the last update couple of months ago.
2013-10-15 18:17:31 +03:00
Athou
bad915bbaa fix warnings 2013-10-14 15:57:35 +02:00
Athou
287dea2d36 use tag for feed name is available 2013-10-14 07:41:19 +02:00
Athou
a0b937769d autofocus the input when it appears 2013-10-14 07:01:14 +02:00
Athou
6acef4a406 fix mark up to link positioning on mobile 2013-10-14 02:44:48 +02:00
Athou
8b77eb9850 add valid checksum (fix #517) 2013-10-14 02:15:03 +02:00
Athou
6f22836dcb remove double slash in image 2013-10-13 21:34:53 +02:00
Athou
a4347c8878 highlight tag if selected 2013-10-13 16:02:59 +02:00
Athou
836f7eff09 better index usage 2013-10-13 12:15:41 +02:00
Athou
c993bd472d cleanup 2013-10-13 12:04:29 +02:00
Athou
431ab92a02 tagging support (#96) 2013-10-13 11:58:22 +02:00
Athou
94f469a6b1 js dependencies update 2013-10-13 10:49:03 +02:00
Athou
3fec1c6890 dependencies update 2013-10-12 17:41:07 +02:00
Athou
f8316911bd another typo 2013-10-12 14:57:01 +02:00
Athou
642d1f6be5 typo 2013-10-12 14:56:24 +02:00
Athou
5a82c3a130 Merge pull request #515 from rthome/master
Translate new strings to German
2013-10-12 00:10:15 -07:00
Raffael Thome
6a8174afac Translate new strings to German 2013-10-12 08:17:49 +02:00
Athou
f4c86634f7 liquibase upgrade, removing dirty fix 2013-10-10 10:07:16 +02:00
Athou
322e588a4e Merge pull request #514 from LpSamuelm/patch-18
Translated new labels to Swedish
2013-10-07 22:06:03 -07:00
Athou
822dee7a13 scale better, don't block when the pool is exhausted 2013-10-08 07:05:31 +02:00
LpSamuelm
101e179788 Translated new labels to Swedish 2013-10-07 21:04:35 +02:00
Athou
57abee6cf0 use the url of the feed as the base url to resolve relative entry links when the declared link in the feed is relative 2013-10-03 12:42:05 +02:00
Athou
b615847b09 make sure entries with same update date are always sorted the same way 2013-10-03 11:05:13 +02:00
Athou
ffef87e249 customizable scrolling speed 2013-10-03 10:40:58 +02:00
Athou
ba3b8df4c9 mark older than half a day 2013-10-03 10:02:16 +02:00
Athou
40175d3e54 dependencies update 2013-09-25 13:51:09 +02:00
Athou
06b047cfe6 1.4.0 snapshot 2013-09-25 13:48:29 +02:00
185 changed files with 5840 additions and 3442 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

130
pom.xml
View File

@@ -4,7 +4,7 @@
<groupId>com.commafeed</groupId>
<artifactId>commafeed</artifactId>
<version>1.3.0</version>
<version>1.5.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>CommaFeed</name>
@@ -79,7 +79,7 @@
<artifactId>tomee-maven-plugin</artifactId>
<version>1.5.2</version>
<configuration>
<tomeeVersion>1.5.2</tomeeVersion>
<tomeeVersion>1.6.0</tomeeVersion>
<tomeeClassifier>plus</tomeeClassifier>
<tomeeHttpPort>8082</tomeeHttpPort>
<args>-Xmx1024m -XX:MaxPermSize=512m -XX:+CMSClassUnloadingEnabled</args>
@@ -104,13 +104,18 @@
<lib>org.hibernate.common:hibernate-commons-annotations:4.0.1.Final</lib>
<lib>org.hibernate:hibernate-validator:4.3.1.Final</lib>
<lib>org.jboss.logging:jboss-logging:3.1.3.GA</lib>
<lib>org.javassist:javassist:3.15.0-GA</lib>
<lib>org.apache.openejb:openejb-bonecp:4.6.0</lib>
<lib>com.jolbox:bonecp:0.8.0.RELEASE</lib>
<lib>com.google.guava:guava:14.0.1</lib>
<lib>dom4j:dom4j:1.6.1</lib>
<lib>antlr:antlr:2.7.7</lib>
<lib>remove:openjpa-</lib>
<lib>remove:hsqldb</lib>
<lib>org.hsqldb:hsqldb:2.3.0</lib>
<lib>mysql:mysql-connector-java:5.1.24</lib>
<lib>mysql:mysql-connector-java:5.1.26</lib>
<lib>postgresql:postgresql:9.1-901.jdbc4</lib>
<lib>net.sourceforge.jtds:jtds:1.3.1</lib>
@@ -169,7 +174,7 @@
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
<version>2.1.5</version>
<version>2.1.7</version>
<executions>
<execution>
<goals>
@@ -189,7 +194,7 @@
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>0.12.0</version>
<version>1.12.6</version>
<scope>provided</scope>
</dependency>
<dependency>
@@ -214,28 +219,28 @@
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.1.0</version>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<version>3.0.2</version>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>14.0.1</version>
<version>16.0.1</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.8.3</version>
<version>1.9.1</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.8</version>
<version>1.9</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
@@ -260,7 +265,7 @@
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3</version>
<version>1.3.1</version>
</dependency>
<dependency>
@@ -298,34 +303,34 @@
<dependency>
<groupId>com.google.gwt</groupId>
<artifactId>gwt-servlet</artifactId>
<version>2.5.1</version>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>net.sourceforge.cssparser</groupId>
<artifactId>cssparser</artifactId>
<version>0.9.9</version>
<version>0.9.13</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.2.5</version>
<version>4.3.3</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.7.2</version>
<version>1.7.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.2.2</version>
<version>2.3.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
@@ -336,28 +341,28 @@
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket-core</artifactId>
<version>6.10.0</version>
<version>6.14.0</version>
</dependency>
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket-auth-roles</artifactId>
<version>6.10.0</version>
<version>6.14.0</version>
</dependency>
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket-extensions</artifactId>
<version>6.10.0</version>
<version>6.14.0</version>
</dependency>
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket-cdi</artifactId>
<version>6.10.0</version>
<version>6.14.0</version>
</dependency>
<dependency>
<groupId>ro.isdc.wro4j</groupId>
<artifactId>wro4j-extensions</artifactId>
<version>1.6.3</version>
<version>1.7.5</version>
</dependency>
<dependency>
@@ -375,12 +380,83 @@
<dependency>
<groupId>com.codahale.metrics</groupId>
<artifactId>metrics-core</artifactId>
<version>3.0.1</version>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>com.codahale.metrics</groupId>
<artifactId>metrics-json</artifactId>
<version>3.0.1</version>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>lodash</artifactId>
<version>2.4.1-3</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>1.11.0</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery-mousewheel</artifactId>
<version>3.1.9</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>angularjs</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>angular-ui-router</artifactId>
<version>0.2.8-2</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>angular-ui-utils</artifactId>
<version>0.1.0</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>ui-select2</artifactId>
<version>0.0.5</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>angular-ui-bootstrap</artifactId>
<version>0.2.0</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>mousetrap</artifactId>
<version>1.4.6</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>momentjs</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>ng-grid</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>device.js</artifactId>
<version>139f208</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>ngInfiniteScroll</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
@@ -402,7 +478,7 @@
<plugin>
<groupId>ro.isdc.wro4j</groupId>
<artifactId>wro4j-maven-plugin</artifactId>
<version>1.6.3</version>
<version>1.7.5</version>
<executions>
<execution>
<id>js</id>
@@ -412,7 +488,7 @@
</goals>
<configuration>
<targetGroups>app</targetGroups>
<options>indent,devel,noarg,quotmark,laxcomma,laxbreak</options>
<options>devel,noarg,quotmark,laxcomma,laxbreak</options>
</configuration>
</execution>
<execution>
@@ -549,7 +625,7 @@
<plugin>
<groupId>ro.isdc.wro4j</groupId>
<artifactId>wro4j-maven-plugin</artifactId>
<version>1.6.3</version>
<version>1.7.5</version>
<executions>
<execution>
<goals>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,13 +6,19 @@ import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.SetJoin;
import org.apache.commons.codec.digest.DigestUtils;
import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.FeedEntry;
import com.commafeed.backend.model.FeedEntry_;
import com.commafeed.backend.model.FeedSubscription;
import com.commafeed.backend.model.FeedSubscription_;
import com.commafeed.backend.model.Feed_;
import com.google.common.collect.Iterables;
@@ -36,6 +42,34 @@ public class FeedEntryDAO extends GenericDAO<FeedEntry> {
return Iterables.getFirst(list, null);
}
public List<FeedEntry> findWithoutSubscriptions(int max) {
CriteriaQuery<FeedEntry> query = builder.createQuery(getType());
Root<FeedEntry> root = query.from(getType());
Join<FeedEntry, Feed> feedJoin = root.join(FeedEntry_.feed);
SetJoin<Feed, FeedSubscription> subJoin = feedJoin.join(Feed_.subscriptions, JoinType.LEFT);
query.where(builder.isNull(subJoin.get(FeedSubscription_.id)));
TypedQuery<FeedEntry> q = em.createQuery(query);
q.setMaxResults(max);
return q.getResultList();
}
public int delete(Feed feed, int max) {
CriteriaQuery<FeedEntry> query = builder.createQuery(getType());
Root<FeedEntry> root = query.from(getType());
query.where(builder.equal(root.get(FeedEntry_.feed), feed));
TypedQuery<FeedEntry> q = em.createQuery(query);
q.setMaxResults(max);
List<FeedEntry> list = q.getResultList();
int deleted = list.size();
delete(list);
return deleted;
}
public int delete(Date olderThan, int max) {
CriteriaQuery<FeedEntry> query = builder.createQuery(getType());
Root<FeedEntry> root = query.from(getType());

View File

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

View File

@@ -0,0 +1,43 @@
package com.commafeed.backend.dao;
import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import com.commafeed.backend.model.FeedEntry;
import com.commafeed.backend.model.FeedEntryTag;
import com.commafeed.backend.model.FeedEntryTag_;
import com.commafeed.backend.model.FeedEntry_;
import com.commafeed.backend.model.User;
import com.commafeed.backend.model.User_;
@Stateless
public class FeedEntryTagDAO extends GenericDAO<FeedEntryTag> {
public List<String> findByUser(User user) {
CriteriaQuery<String> query = builder.createQuery(String.class);
Root<FeedEntryTag> root = query.from(getType());
query.select(root.get(FeedEntryTag_.name));
query.distinct(true);
Predicate p1 = builder.equal(root.get(FeedEntryTag_.user).get(User_.id), user.getId());
query.where(p1);
return cache(em.createQuery(query)).getResultList();
}
public List<FeedEntryTag> findByEntry(User user, FeedEntry entry) {
CriteriaQuery<FeedEntryTag> query = builder.createQuery(getType());
Root<FeedEntryTag> root = query.from(getType());
Predicate p1 = builder.equal(root.get(FeedEntryTag_.user).get(User_.id), user.getId());
Predicate p2 = builder.equal(root.get(FeedEntryTag_.entry).get(FeedEntry_.id), entry.getId());
query.where(p1, p2);
return cache(em.createQuery(query)).getResultList();
}
}

View File

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

View File

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

View File

@@ -55,6 +55,7 @@ public class FeedParser {
if (xmlString == null) {
throw new FeedException("Input string is null for url " + feedUrl);
}
xmlString = FeedUtils.replaceHtmlEntitiesWithNumericEntities(xmlString);
InputSource source = new InputSource(new StringReader(xmlString));
SyndFeed rss = new SyndFeedInput().build(source);
handleForeignMarkup(rss);
@@ -66,10 +67,6 @@ public class FeedParser {
feed.setLink(rss.getLink());
List<SyndEntry> items = rss.getEntries();
if (items.isEmpty()) {
throw new FeedException("No items in the feed.");
}
for (SyndEntry item : items) {
FeedEntry entry = new FeedEntry();
@@ -82,8 +79,13 @@ public class FeedParser {
continue;
}
entry.setGuid(FeedUtils.truncate(guid, 2048));
entry.setUrl(FeedUtils.truncate(FeedUtils.toAbsoluteUrl(item.getLink(), feed.getLink()), 2048));
entry.setUpdated(validateDate(getEntryUpdateDate(item), true));
entry.setUrl(FeedUtils.truncate(FeedUtils.toAbsoluteUrl(item.getLink(), feed.getLink(), feed.getUrlAfterRedirect()), 2048));
// if link is empty but guid is used as url
if (StringUtils.isBlank(entry.getUrl()) && StringUtils.startsWith(entry.getGuid(), "http")) {
entry.setUrl(entry.getGuid());
}
FeedEntryContent content = new FeedEntryContent();
content.setContent(getContent(item));

View File

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

View File

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

View File

@@ -1,6 +1,8 @@
package com.commafeed.backend.feeds;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
@@ -15,6 +17,7 @@ import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.math.stat.descriptive.SummaryStatistics;
import org.apache.wicket.request.UrlUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Document.OutputSettings;
@@ -50,6 +53,8 @@ public class FeedUtils {
private static final List<String> ALLOWED_IMG_CSS_RULES = Arrays.asList("display", "width", "height");
private static final char[] FORBIDDEN_CSS_RULE_CHARACTERS = new char[] { '(', ')' };
private static final Whitelist WHITELIST = buildWhiteList();
public static String truncate(String string, int length) {
if (string != null) {
string = string.substring(0, Math.min(length, string.length()));
@@ -57,6 +62,39 @@ public class FeedUtils {
return string;
}
private static synchronized Whitelist buildWhiteList() {
Whitelist whitelist = new Whitelist();
whitelist.addTags("a", "b", "blockquote", "br", "caption", "cite", "code", "col", "colgroup", "dd", "div", "dl", "dt", "em", "h1",
"h2", "h3", "h4", "h5", "h6", "i", "iframe", "img", "li", "ol", "p", "pre", "q", "small", "strike", "strong", "sub", "sup",
"table", "tbody", "td", "tfoot", "th", "thead", "tr", "u", "ul");
whitelist.addAttributes("div", "dir");
whitelist.addAttributes("pre", "dir");
whitelist.addAttributes("code", "dir");
whitelist.addAttributes("table", "dir");
whitelist.addAttributes("p", "dir");
whitelist.addAttributes("a", "href", "title");
whitelist.addAttributes("blockquote", "cite");
whitelist.addAttributes("col", "span", "width");
whitelist.addAttributes("colgroup", "span", "width");
whitelist.addAttributes("iframe", "src", "height", "width", "allowfullscreen", "frameborder", "style");
whitelist.addAttributes("img", "align", "alt", "height", "src", "title", "width", "style");
whitelist.addAttributes("ol", "start", "type");
whitelist.addAttributes("q", "cite");
whitelist.addAttributes("table", "border", "bordercolor", "summary", "width");
whitelist.addAttributes("td", "border", "bordercolor", "abbr", "axis", "colspan", "rowspan", "width");
whitelist.addAttributes("th", "border", "bordercolor", "abbr", "axis", "colspan", "rowspan", "scope", "width");
whitelist.addAttributes("ul", "type");
whitelist.addProtocols("a", "href", "ftp", "http", "https", "mailto");
whitelist.addProtocols("blockquote", "cite", "http", "https");
whitelist.addProtocols("img", "src", "http", "https");
whitelist.addProtocols("q", "cite", "http", "https");
whitelist.addEnforcedAttribute("a", "target", "_blank");
return whitelist;
}
/**
* Detect feed encoding by using the declared encoding in the xml processing instruction and by detecting the characters used in the
* feed
@@ -91,6 +129,14 @@ public class FeedUtils {
}
return encoding;
}
public static String replaceHtmlEntitiesWithNumericEntities(String source){
String result = source;
for(String entity : HtmlEntities.NUMERIC_MAPPING.keySet()){
result = result.replace(entity, HtmlEntities.NUMERIC_MAPPING.get(entity));
}
return result;
}
/**
* Normalize the url. The resulting url is not meant to be fetched but rather used as a mean to identify a feed and avoid duplicates
@@ -152,38 +198,9 @@ public class FeedUtils {
public static String handleContent(String content, String baseUri, boolean keepTextOnly) {
if (StringUtils.isNotBlank(content)) {
baseUri = StringUtils.trimToEmpty(baseUri);
Whitelist whitelist = new Whitelist();
whitelist.addTags("a", "b", "blockquote", "br", "caption", "cite", "code", "col", "colgroup", "dd", "div", "dl", "dt", "em",
"h1", "h2", "h3", "h4", "h5", "h6", "i", "iframe", "img", "li", "ol", "p", "pre", "q", "small", "strike", "strong",
"sub", "sup", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "u", "ul");
whitelist.addAttributes("div", "dir");
whitelist.addAttributes("pre", "dir");
whitelist.addAttributes("code", "dir");
whitelist.addAttributes("table", "dir");
whitelist.addAttributes("p", "dir");
whitelist.addAttributes("a", "href", "title");
whitelist.addAttributes("blockquote", "cite");
whitelist.addAttributes("col", "span", "width");
whitelist.addAttributes("colgroup", "span", "width");
whitelist.addAttributes("iframe", "src", "height", "width", "allowfullscreen", "frameborder", "style");
whitelist.addAttributes("img", "align", "alt", "height", "src", "title", "width", "style");
whitelist.addAttributes("ol", "start", "type");
whitelist.addAttributes("q", "cite");
whitelist.addAttributes("table", "border", "bordercolor", "summary", "width");
whitelist.addAttributes("td", "border", "bordercolor", "abbr", "axis", "colspan", "rowspan", "width");
whitelist.addAttributes("th", "border", "bordercolor", "abbr", "axis", "colspan", "rowspan", "scope", "width");
whitelist.addAttributes("ul", "type");
whitelist.addProtocols("a", "href", "ftp", "http", "https", "mailto");
whitelist.addProtocols("blockquote", "cite", "http", "https");
whitelist.addProtocols("img", "src", "http", "https");
whitelist.addProtocols("q", "cite", "http", "https");
whitelist.addEnforcedAttribute("a", "target", "_blank");
Document dirty = Jsoup.parseBodyFragment(content, baseUri);
Cleaner cleaner = new Cleaner(whitelist);
Cleaner cleaner = new Cleaner(WHITELIST);
Document clean = cleaner.clean(dirty);
for (Element e : clean.select("iframe[style]")) {
@@ -389,17 +406,37 @@ public class FeedUtils {
return url;
}
public static String toAbsoluteUrl(String url, String baseUrl) {
/**
*
* @param url
* the url of the entry
* @param feedLink
* the url of the feed as described in the feed
* @param feedUrl
* the url of the feed that we used to fetch the feed
* @return an absolute url pointing to the entry
*/
public static String toAbsoluteUrl(String url, String feedLink, String feedUrl) {
url = StringUtils.trimToNull(StringUtils.normalizeSpace(url));
if (baseUrl == null || url == null || url.startsWith("http")) {
if (url == null || url.startsWith("http")) {
return url;
}
if (url.startsWith("/") == false) {
url = "/" + url;
String baseUrl = (feedLink == null || UrlUtils.isRelative(feedLink)) ? feedUrl : feedLink;
if (baseUrl == null) {
return url;
}
return baseUrl + url;
String result = null;
try {
result = new URL(new URL(baseUrl), url).toString();
} catch (MalformedURLException e) {
log.debug("could not parse url : " + e.getMessage(), e);
result = url;
}
return result;
}
public static String getFaviconUrl(FeedSubscription subscription, String publicUrl) {

View File

@@ -0,0 +1,266 @@
package com.commafeed.backend.feeds;
import java.util.Collections;
import java.util.Map;
import com.google.gwt.thirdparty.guava.common.collect.Maps;
public class HtmlEntities {
public static final Map<String, String> NUMERIC_MAPPING = Collections.unmodifiableMap(loadMap());
private static synchronized Map<String, String> loadMap() {
Map<String, String> map = Maps.newLinkedHashMap();
map.put("&Aacute;", "&#193;");
map.put("&aacute;", "&#225;");
map.put("&Acirc;", "&#194;");
map.put("&acirc;", "&#226;");
map.put("&acute;", "&#180;");
map.put("&AElig;", "&#198;");
map.put("&aelig;", "&#230;");
map.put("&Agrave;", "&#192;");
map.put("&agrave;", "&#224;");
map.put("&alefsym;", "&#8501;");
map.put("&Alpha;", "&#913;");
map.put("&alpha;", "&#945;");
map.put("&amp;", "&#38;");
map.put("&and;", "&#8743;");
map.put("&ang;", "&#8736;");
map.put("&Aring;", "&#197;");
map.put("&aring;", "&#229;");
map.put("&asymp;", "&#8776;");
map.put("&Atilde;", "&#195;");
map.put("&atilde;", "&#227;");
map.put("&Auml;", "&#196;");
map.put("&auml;", "&#228;");
map.put("&bdquo;", "&#8222;");
map.put("&Beta;", "&#914;");
map.put("&beta;", "&#946;");
map.put("&brvbar;", "&#166;");
map.put("&bull;", "&#8226;");
map.put("&cap;", "&#8745;");
map.put("&Ccedil;", "&#199;");
map.put("&ccedil;", "&#231;");
map.put("&cedil;", "&#184;");
map.put("&cent;", "&#162;");
map.put("&Chi;", "&#935;");
map.put("&chi;", "&#967;");
map.put("&circ;", "&#710;");
map.put("&clubs;", "&#9827;");
map.put("&cong;", "&#8773;");
map.put("&copy;", "&#169;");
map.put("&crarr;", "&#8629;");
map.put("&cup;", "&#8746;");
map.put("&curren;", "&#164;");
map.put("&dagger;", "&#8224;");
map.put("&Dagger;", "&#8225;");
map.put("&darr;", "&#8595;");
map.put("&dArr;", "&#8659;");
map.put("&deg;", "&#176;");
map.put("&Delta;", "&#916;");
map.put("&delta;", "&#948;");
map.put("&diams;", "&#9830;");
map.put("&divide;", "&#247;");
map.put("&Eacute;", "&#201;");
map.put("&eacute;", "&#233;");
map.put("&Ecirc;", "&#202;");
map.put("&ecirc;", "&#234;");
map.put("&Egrave;", "&#200;");
map.put("&egrave;", "&#232;");
map.put("&empty;", "&#8709;");
map.put("&emsp;", "&#8195;");
map.put("&ensp;", "&#8194;");
map.put("&Epsilon;", "&#917;");
map.put("&epsilon;", "&#949;");
map.put("&equiv;", "&#8801;");
map.put("&Eta;", "&#919;");
map.put("&eta;", "&#951;");
map.put("&ETH;", "&#208;");
map.put("&eth;", "&#240;");
map.put("&Euml;", "&#203;");
map.put("&euml;", "&#235;");
map.put("&euro;", "&#8364;");
map.put("&exist;", "&#8707;");
map.put("&fnof;", "&#402;");
map.put("&forall;", "&#8704;");
map.put("&frac12;", "&#189;");
map.put("&frac14;", "&#188;");
map.put("&frac34;", "&#190;");
map.put("&frasl;", "&#8260;");
map.put("&Gamma;", "&#915;");
map.put("&gamma;", "&#947;");
map.put("&ge;", "&#8805;");
map.put("&harr;", "&#8596;");
map.put("&hArr;", "&#8660;");
map.put("&hearts;", "&#9829;");
map.put("&hellip;", "&#8230;");
map.put("&Iacute;", "&#205;");
map.put("&iacute;", "&#237;");
map.put("&Icirc;", "&#206;");
map.put("&icirc;", "&#238;");
map.put("&iexcl;", "&#161;");
map.put("&Igrave;", "&#204;");
map.put("&igrave;", "&#236;");
map.put("&image;", "&#8465;");
map.put("&infin;", "&#8734;");
map.put("&int;", "&#8747;");
map.put("&Iota;", "&#921;");
map.put("&iota;", "&#953;");
map.put("&iquest;", "&#191;");
map.put("&isin;", "&#8712;");
map.put("&Iuml;", "&#207;");
map.put("&iuml;", "&#239;");
map.put("&Kappa;", "&#922;");
map.put("&kappa;", "&#954;");
map.put("&Lambda;", "&#923;");
map.put("&lambda;", "&#955;");
map.put("&lang;", "&#9001;");
map.put("&laquo;", "&#171;");
map.put("&larr;", "&#8592;");
map.put("&lArr;", "&#8656;");
map.put("&lceil;", "&#8968;");
map.put("&ldquo;", "&#8220;");
map.put("&le;", "&#8804;");
map.put("&lfloor;", "&#8970;");
map.put("&lowast;", "&#8727;");
map.put("&loz;", "&#9674;");
map.put("&lrm;", "&#8206;");
map.put("&lsaquo;", "&#8249;");
map.put("&lsquo;", "&#8216;");
map.put("&macr;", "&#175;");
map.put("&mdash;", "&#8212;");
map.put("&micro;", "&#181;");
map.put("&middot;", "&#183;");
map.put("&minus;", "&#8722;");
map.put("&Mu;", "&#924;");
map.put("&mu;", "&#956;");
map.put("&nabla;", "&#8711;");
map.put("&nbsp;", "&#160;");
map.put("&ndash;", "&#8211;");
map.put("&ne;", "&#8800;");
map.put("&ni;", "&#8715;");
map.put("&not;", "&#172;");
map.put("&notin;", "&#8713;");
map.put("&nsub;", "&#8836;");
map.put("&Ntilde;", "&#209;");
map.put("&ntilde;", "&#241;");
map.put("&Nu;", "&#925;");
map.put("&nu;", "&#957;");
map.put("&Oacute;", "&#211;");
map.put("&oacute;", "&#243;");
map.put("&Ocirc;", "&#212;");
map.put("&ocirc;", "&#244;");
map.put("&OElig;", "&#338;");
map.put("&oelig;", "&#339;");
map.put("&Ograve;", "&#210;");
map.put("&ograve;", "&#242;");
map.put("&oline;", "&#8254;");
map.put("&Omega;", "&#937;");
map.put("&omega;", "&#969;");
map.put("&Omicron;", "&#927;");
map.put("&omicron;", "&#959;");
map.put("&oplus;", "&#8853;");
map.put("&or;", "&#8744;");
map.put("&ordf;", "&#170;");
map.put("&ordm;", "&#186;");
map.put("&Oslash;", "&#216;");
map.put("&oslash;", "&#248;");
map.put("&Otilde;", "&#213;");
map.put("&otilde;", "&#245;");
map.put("&otimes;", "&#8855;");
map.put("&Ouml;", "&#214;");
map.put("&ouml;", "&#246;");
map.put("&para;", "&#182;");
map.put("&part;", "&#8706;");
map.put("&permil;", "&#8240;");
map.put("&perp;", "&#8869;");
map.put("&Phi;", "&#934;");
map.put("&phi;", "&#966;");
map.put("&Pi;", "&#928;");
map.put("&pi;", "&#960;");
map.put("&piv;", "&#982;");
map.put("&plusmn;", "&#177;");
map.put("&pound;", "&#163;");
map.put("&prime;", "&#8242;");
map.put("&Prime;", "&#8243;");
map.put("&prod;", "&#8719;");
map.put("&prop;", "&#8733;");
map.put("&Psi;", "&#936;");
map.put("&psi;", "&#968;");
map.put("&quot;", "&#34;");
map.put("&radic;", "&#8730;");
map.put("&rang;", "&#9002;");
map.put("&raquo;", "&#187;");
map.put("&rarr;", "&#8594;");
map.put("&rArr;", "&#8658;");
map.put("&rceil;", "&#8969;");
map.put("&rdquo;", "&#8221;");
map.put("&real;", "&#8476;");
map.put("&reg;", "&#174;");
map.put("&rfloor;", "&#8971;");
map.put("&Rho;", "&#929;");
map.put("&rho;", "&#961;");
map.put("&rlm;", "&#8207;");
map.put("&rsaquo;", "&#8250;");
map.put("&rsquo;", "&#8217;");
map.put("&sbquo;", "&#8218;");
map.put("&Scaron;", "&#352;");
map.put("&scaron;", "&#353;");
map.put("&sdot;", "&#8901;");
map.put("&sect;", "&#167;");
map.put("&shy;", "&#173;");
map.put("&Sigma;", "&#931;");
map.put("&sigma;", "&#963;");
map.put("&sigmaf;", "&#962;");
map.put("&sim;", "&#8764;");
map.put("&spades;", "&#9824;");
map.put("&sub;", "&#8834;");
map.put("&sube;", "&#8838;");
map.put("&sum;", "&#8721;");
map.put("&sup1;", "&#185;");
map.put("&sup2;", "&#178;");
map.put("&sup3;", "&#179;");
map.put("&sup;", "&#8835;");
map.put("&supe;", "&#8839;");
map.put("&szlig;", "&#223;");
map.put("&Tau;", "&#932;");
map.put("&tau;", "&#964;");
map.put("&there4;", "&#8756;");
map.put("&Theta;", "&#920;");
map.put("&theta;", "&#952;");
map.put("&thetasym;", "&#977;");
map.put("&thinsp;", "&#8201;");
map.put("&THORN;", "&#222;");
map.put("&thorn;", "&#254;");
map.put("&tilde;", "&#732;");
map.put("&times;", "&#215;");
map.put("&trade;", "&#8482;");
map.put("&Uacute;", "&#218;");
map.put("&uacute;", "&#250;");
map.put("&uarr;", "&#8593;");
map.put("&uArr;", "&#8657;");
map.put("&Ucirc;", "&#219;");
map.put("&ucirc;", "&#251;");
map.put("&Ugrave;", "&#217;");
map.put("&ugrave;", "&#249;");
map.put("&uml;", "&#168;");
map.put("&upsih;", "&#978;");
map.put("&Upsilon;", "&#933;");
map.put("&upsilon;", "&#965;");
map.put("&Uuml;", "&#220;");
map.put("&uuml;", "&#252;");
map.put("&weierp;", "&#8472;");
map.put("&Xi;", "&#926;");
map.put("&xi;", "&#958;");
map.put("&Yacute;", "&#221;");
map.put("&yacute;", "&#253;");
map.put("&yen;", "&#165;");
map.put("&yuml;", "&#255;");
map.put("&Yuml;", "&#376;");
map.put("&Zeta;", "&#918;");
map.put("&zeta;", "&#950;");
map.put("&zwj;", "&#8205;");
map.put("&zwnj;", "&#8204;");
return map;
}
}

View File

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

View File

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

View File

@@ -0,0 +1,46 @@
package com.commafeed.backend.model;
import javax.persistence.Cacheable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
@Entity
@Table(name = "FEEDENTRYTAGS")
@SuppressWarnings("serial")
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
@Getter
@Setter
public class FeedEntryTag extends AbstractModel {
@JoinColumn(name = "user_id")
@ManyToOne(fetch = FetchType.LAZY)
private User user;
@JoinColumn(name = "entry_id")
@ManyToOne(fetch = FetchType.LAZY)
private FeedEntry entry;
@Column(name = "name", length = 40)
private String name;
public FeedEntryTag() {
}
public FeedEntryTag(User user, FeedEntry entry, String name) {
this.name = name;
this.entry = entry;
this.user = user;
}
}

View File

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

View File

@@ -59,7 +59,6 @@ public class UserSettings extends AbstractModel {
private boolean showRead;
private boolean scrollMarks;
private boolean socialButtons;
@Column(length = 32)
private String theme;
@@ -68,4 +67,18 @@ public class UserSettings extends AbstractModel {
@Column(length = Integer.MAX_VALUE)
private String customCss;
@Column(name = "scroll_speed")
private int scrollSpeed;
private boolean email;
private boolean gmail;
private boolean facebook;
private boolean twitter;
private boolean googleplus;
private boolean tumblr;
private boolean pocket;
private boolean instapaper;
private boolean buffer;
private boolean readability;
}

View File

@@ -34,9 +34,14 @@ public class OPMLExporter {
List<FeedCategory> categories = feedCategoryDAO.findAll(user);
List<FeedSubscription> subscriptions = feedSubscriptionDAO.findAll(user);
// export root categories
for (FeedCategory cat : categories) {
opml.getOutlines().add(buildCategoryOutline(cat, subscriptions));
if (cat.getParent() == null) {
opml.getOutlines().add(buildCategoryOutline(cat, subscriptions));
}
}
// export root subscriptions
for (FeedSubscription sub : subscriptions) {
if (sub.getCategory() == null) {
opml.getOutlines().add(buildSubscriptionOutline(sub));

View File

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

View File

@@ -16,6 +16,7 @@ import com.commafeed.backend.dao.FeedEntryDAO;
import com.commafeed.backend.dao.FeedEntryStatusDAO;
import com.commafeed.backend.dao.FeedSubscriptionDAO;
import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.FeedEntry;
import com.commafeed.backend.model.FeedEntryStatus;
import com.commafeed.backend.model.FeedSubscription;
@@ -26,6 +27,8 @@ import com.commafeed.backend.model.FeedSubscription;
@Slf4j
public class DatabaseCleaningService {
private static final int BATCH_SIZE = 100;
@Inject
FeedDAO feedDAO;
@@ -43,13 +46,28 @@ public class DatabaseCleaningService {
@Inject
ApplicationSettingsService applicationSettingsService;
public long cleanEntriesWithoutSubscriptions() {
log.info("cleaning entries without subscriptions");
long total = 0;
int deleted = 0;
do {
List<FeedEntry> entries = feedEntryDAO.findWithoutSubscriptions(BATCH_SIZE);
deleted = feedEntryDAO.delete(entries);
total += deleted;
log.info("removed {} entries without subscriptions", total);
} while (deleted != 0);
log.info("cleanup done: {} entries without subscriptions deleted", total);
return total;
}
public long cleanFeedsWithoutSubscriptions() {
log.info("cleaning feeds without subscriptions");
long total = 0;
int deleted = -1;
int deleted = 0;
do {
deleted = feedDAO.deleteWithoutSubscriptions(10);
List<Feed> feeds = feedDAO.findWithoutSubscriptions(BATCH_SIZE);
deleted = feedDAO.delete(feeds);
total += deleted;
log.info("removed {} feeds without subscriptions", total);
} while (deleted != 0);
@@ -58,15 +76,15 @@ public class DatabaseCleaningService {
}
public long cleanContentsWithoutEntries() {
log.info("cleaning contents without entries");
long total = 0;
int deleted = -1;
int deleted = 0;
do {
deleted = feedEntryContentDAO.deleteWithoutEntries(10);
deleted = feedEntryContentDAO.deleteWithoutEntries(BATCH_SIZE);
total += deleted;
log.info("removed {} feeds without subscriptions", total);
log.info("removed {} contents without entries", total);
} while (deleted != 0);
log.info("cleanup done: {} feeds without subscriptions deleted", total);
log.info("cleanup done: {} contents without entries deleted", total);
return total;
}
@@ -75,9 +93,9 @@ public class DatabaseCleaningService {
cal.add(Calendar.MINUTE, -1 * (int) unit.toMinutes(value));
long total = 0;
int deleted = -1;
int deleted = 0;
do {
deleted = feedEntryDAO.delete(cal.getTime(), 100);
deleted = feedEntryDAO.delete(cal.getTime(), BATCH_SIZE);
total += deleted;
log.info("removed {} entries", total);
} while (deleted != 0);
@@ -105,7 +123,7 @@ public class DatabaseCleaningService {
long total = 0;
List<FeedEntryStatus> list = Collections.emptyList();
do {
list = feedEntryStatusDAO.getOldStatuses(olderThan, 100);
list = feedEntryStatusDAO.getOldStatuses(olderThan, BATCH_SIZE);
if (!list.isEmpty()) {
feedEntryStatusDAO.delete(list);
total += list.size();

View File

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

View File

@@ -0,0 +1,61 @@
package com.commafeed.backend.services;
import java.util.List;
import java.util.Map;
import javax.ejb.Stateless;
import javax.inject.Inject;
import com.commafeed.backend.dao.FeedEntryDAO;
import com.commafeed.backend.dao.FeedEntryTagDAO;
import com.commafeed.backend.model.FeedEntry;
import com.commafeed.backend.model.FeedEntryTag;
import com.commafeed.backend.model.User;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@Stateless
public class FeedEntryTagService {
@Inject
FeedEntryDAO feedEntryDAO;
@Inject
FeedEntryTagDAO feedEntryTagDAO;
public void updateTags(User user, Long entryId, List<String> tagNames) {
FeedEntry entry = feedEntryDAO.findById(entryId);
if (entry == null) {
return;
}
List<FeedEntryTag> tags = feedEntryTagDAO.findByEntry(user, entry);
Map<String, FeedEntryTag> tagMap = Maps.uniqueIndex(tags, new Function<FeedEntryTag, String>() {
@Override
public String apply(FeedEntryTag input) {
return input.getName();
}
});
List<FeedEntryTag> addList = Lists.newArrayList();
List<FeedEntryTag> removeList = Lists.newArrayList();
for (String tagName : tagNames) {
FeedEntryTag tag = tagMap.get(tagName);
if (tag == null) {
addList.add(new FeedEntryTag(user, entry, tagName));
}
}
for (FeedEntryTag tag : tags) {
if (!tagNames.contains(tag.getName())) {
removeList.add(tag);
}
}
feedEntryTagDAO.saveOrUpdate(addList);
feedEntryTagDAO.delete(removeList);
}
}

View File

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

View File

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

View File

@@ -41,4 +41,7 @@ public class Entries implements Serializable {
@ApiProperty("list of entries")
private List<Entry> entries = Lists.newArrayList();
@ApiProperty("if true, the unread flag was ignored in the request, all entries are returned regardless of their read status")
private boolean ignoredReadStatus;
}

View File

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

View File

@@ -27,9 +27,6 @@ public class Settings implements Serializable {
@ApiProperty(value = "user wants category and feeds with no unread entries shown", required = true)
private boolean showRead;
@ApiProperty(value = "user wants social buttons (facebook, twitter, ...) shown", required = true)
private boolean socialButtons;
@ApiProperty(value = "In expanded view, scroll through entries mark them as read", required = true)
private boolean scrollMarks;
@@ -38,5 +35,19 @@ public class Settings implements Serializable {
@ApiProperty(value = "user's custom css for the website")
private String customCss;
@ApiProperty(value = "user's preferred scroll speed when navigating between entries")
private int scrollSpeed;
private boolean email;
private boolean gmail;
private boolean facebook;
private boolean twitter;
private boolean googleplus;
private boolean tumblr;
private boolean pocket;
private boolean instapaper;
private boolean buffer;
private boolean readability;
}

View File

@@ -0,0 +1,22 @@
package com.commafeed.frontend.model.request;
import java.io.Serializable;
import java.util.List;
import lombok.Data;
import com.wordnik.swagger.annotations.ApiClass;
import com.wordnik.swagger.annotations.ApiProperty;
@SuppressWarnings("serial")
@ApiClass("Tag Request")
@Data
public class TagRequest implements Serializable {
@ApiProperty(value = "entry id", required = true)
private Long entryId;
@ApiProperty(value = "tags")
private List<String> tags;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,18 @@
package com.commafeed.frontend.resources;
import ro.isdc.wro.model.resource.processor.impl.css.CssUrlRewritingProcessor;
public class CustomCssUrlRewritingProcessor extends CssUrlRewritingProcessor {
/**
* ignore webjar image replacements since they won't be available at runtime anyway
*/
@Override
protected String replaceImageUrl(String cssUri, String imageUrl) {
if (cssUri.startsWith("webjar:")) {
return imageUrl;
}
return super.replaceImageUrl(cssUri, imageUrl);
}
}

View File

@@ -20,6 +20,7 @@ public class WroAdditionalProvider implements ProcessorProvider {
map.put("sassOnlyProcessor", new SassOnlyProcessor());
map.put("sassImport", new SassImportProcessor());
map.put("timestamp", new TimestampProcessor());
map.put("cssUrlRewriting", new CustomCssUrlRewritingProcessor());
return map;
}

View File

@@ -16,6 +16,7 @@ public class WroManagerFactory extends ConfigurableWroManagerFactory {
map.put("sassOnlyProcessor", new SassOnlyProcessor());
map.put("sassImport", new SassImportProcessor());
map.put("timestamp", new TimestampProcessor());
map.put("cssUrlRewriting", new CustomCssUrlRewritingProcessor());
}
}

View File

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

View File

@@ -1,17 +1,13 @@
package com.commafeed.frontend.rest.resources;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
@@ -19,14 +15,12 @@ import org.apache.commons.lang.StringUtils;
import com.codahale.metrics.MetricRegistry;
import com.commafeed.backend.dao.FeedDAO;
import com.commafeed.backend.dao.FeedDAO.DuplicateMode;
import com.commafeed.backend.dao.UserDAO;
import com.commafeed.backend.dao.UserRoleDAO;
import com.commafeed.backend.feeds.FeedRefreshTaskGiver;
import com.commafeed.backend.feeds.FeedRefreshUpdater;
import com.commafeed.backend.feeds.FeedRefreshWorker;
import com.commafeed.backend.model.ApplicationSettings;
import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.User;
import com.commafeed.backend.model.UserRole;
import com.commafeed.backend.model.UserRole.Role;
@@ -37,13 +31,10 @@ import com.commafeed.backend.services.PasswordEncryptionService;
import com.commafeed.backend.services.UserService;
import com.commafeed.backend.startup.StartupBean;
import com.commafeed.frontend.SecurityCheck;
import com.commafeed.frontend.model.FeedCount;
import com.commafeed.frontend.model.UserModel;
import com.commafeed.frontend.model.request.FeedMergeRequest;
import com.commafeed.frontend.model.request.IDRequest;
import com.commafeed.frontend.rest.PrettyPrint;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.wordnik.swagger.annotations.Api;
@@ -233,9 +224,18 @@ public class AdminREST extends AbstractREST {
return Response.ok(metrics).build();
}
@Path("/cleanup/entries")
@GET
@ApiOperation(value = "Entries cleanup", notes = "Delete entries without subscriptions")
public Response cleanupEntries() {
Map<String, Long> map = Maps.newHashMap();
map.put("entries_without_subscriptions", cleaner.cleanEntriesWithoutSubscriptions());
return Response.ok(map).build();
}
@Path("/cleanup/feeds")
@GET
@ApiOperation(value = "Feeds cleanup", notes = "Delete feeds without subscriptions and entries without feeds")
@ApiOperation(value = "Feeds cleanup", notes = "Delete feeds without subscriptions")
public Response cleanupFeeds() {
Map<String, Long> map = Maps.newHashMap();
map.put("feeds_without_subscriptions", cleaner.cleanFeedsWithoutSubscriptions());
@@ -251,44 +251,4 @@ public class AdminREST extends AbstractREST {
return Response.ok(map).build();
}
@Path("/cleanup/entries")
@GET
@ApiOperation(value = "Entries cleanup", notes = "Delete entries older than given date")
public Response cleanupEntries(@QueryParam("days") @DefaultValue("30") int days) {
Map<String, Long> map = Maps.newHashMap();
map.put("old_entries", cleaner.cleanEntriesOlderThan(days, TimeUnit.DAYS));
return Response.ok(map).build();
}
@Path("/cleanup/findDuplicateFeeds")
@GET
@ApiOperation(value = "Find duplicate feeds")
public Response findDuplicateFeeds(@QueryParam("mode") DuplicateMode mode, @QueryParam("page") int page,
@QueryParam("limit") int limit, @QueryParam("minCount") long minCount) {
List<FeedCount> list = feedDAO.findDuplicates(mode, limit * page, limit, minCount);
return Response.ok(list).build();
}
@Path("/cleanup/merge")
@POST
@ApiOperation(value = "Merge feeds", notes = "Merge feeds together")
public Response mergeFeeds(@ApiParam(required = true) FeedMergeRequest request) {
Feed into = feedDAO.findById(request.getIntoFeedId());
if (into == null) {
return Response.status(Status.BAD_REQUEST).entity("'into feed' not found").build();
}
List<Feed> feeds = Lists.newArrayList();
for (Long feedId : request.getFeedIds()) {
Feed feed = feedDAO.findById(feedId);
feeds.add(feed);
}
if (feeds.isEmpty()) {
return Response.status(Status.BAD_REQUEST).entity("'from feeds' empty").build();
}
cleaner.mergeFeeds(into, feeds);
return Response.ok().build();
}
}

View File

@@ -22,8 +22,8 @@ import javax.ws.rs.core.Response.Status;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.ObjectUtils;
import com.commafeed.backend.cache.CacheService;
import com.commafeed.backend.dao.FeedCategoryDAO;
@@ -106,7 +106,8 @@ public class CategoryREST extends AbstractREST {
@ApiParam(
value = "search for keywords in either the title or the content of the entries, separated by spaces, 3 characters minimum") @QueryParam("keywords") String keywords,
@ApiParam(value = "return only entry ids") @DefaultValue("false") @QueryParam("onlyIds") boolean onlyIds,
@ApiParam(value = "comma-separated list of excluded subscription ids") @QueryParam("excludedSubscriptionIds") String excludedSubscriptionIds) {
@ApiParam(value = "comma-separated list of excluded subscription ids") @QueryParam("excludedSubscriptionIds") String excludedSubscriptionIds,
@ApiParam(value = "keep only entries tagged with this tag") @QueryParam("tag") String tag) {
Preconditions.checkNotNull(readType);
@@ -135,11 +136,11 @@ public class CategoryREST extends AbstractREST {
}
if (ALL.equals(id)) {
entries.setName("All");
entries.setName(ObjectUtils.defaultIfNull(tag, "All"));
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(getUser());
removeExcludedSubscriptions(subs, excludedIds);
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(subs, unreadOnly, keywords, newerThanDate, offset,
limit + 1, order, true, onlyIds);
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(getUser(), subs, unreadOnly, keywords, newerThanDate,
offset, limit + 1, order, true, onlyIds, tag);
for (FeedEntryStatus status : list) {
entries.getEntries().add(
@@ -161,8 +162,8 @@ public class CategoryREST extends AbstractREST {
List<FeedCategory> categories = feedCategoryDAO.findAllChildrenCategories(getUser(), parent);
List<FeedSubscription> subs = feedSubscriptionDAO.findByCategories(getUser(), categories);
removeExcludedSubscriptions(subs, excludedIds);
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(subs, unreadOnly, keywords, newerThanDate, offset,
limit + 1, order, true, onlyIds);
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(getUser(), subs, unreadOnly, keywords, newerThanDate,
offset, limit + 1, order, true, onlyIds, tag);
for (FeedEntryStatus status : list) {
entries.getEntries().add(
@@ -182,6 +183,7 @@ public class CategoryREST extends AbstractREST {
}
entries.setTimestamp(System.currentTimeMillis());
entries.setIgnoredReadStatus(STARRED.equals(id) || keywords != null || tag != null);
FeedUtils.removeUnwantedFromSearch(entries.getEntries(), keywords);
return Response.ok(entries).build();
}
@@ -192,16 +194,20 @@ public class CategoryREST extends AbstractREST {
@Produces(MediaType.APPLICATION_XML)
@SecurityCheck(value = Role.USER, apiKeyAllowed = true)
public Response getCategoryEntriesAsFeed(
@ApiParam(value = "id of the category, 'all' or 'starred'", required = true) @QueryParam("id") String id) {
@ApiParam(value = "id of the category, 'all' or 'starred'", required = true) @QueryParam("id") String id,
@ApiParam(value = "all entries or only unread ones", allowableValues = "all,unread", required = true) @DefaultValue("all") @QueryParam("readType") ReadingMode readType,
@ApiParam(value = "only entries newer than this") @QueryParam("newerThan") Long newerThan,
@ApiParam(value = "offset for paging") @DefaultValue("0") @QueryParam("offset") int offset,
@ApiParam(value = "limit for paging, default 20, maximum 1000") @DefaultValue("20") @QueryParam("limit") int limit,
@ApiParam(value = "date ordering", allowableValues = "asc,desc") @QueryParam("order") @DefaultValue("desc") ReadingOrder order,
@ApiParam(
value = "search for keywords in either the title or the content of the entries, separated by spaces, 3 characters minimum") @QueryParam("keywords") String keywords,
@ApiParam(value = "return only entry ids") @DefaultValue("false") @QueryParam("onlyIds") boolean onlyIds,
@ApiParam(value = "comma-separated list of excluded subscription ids") @QueryParam("excludedSubscriptionIds") String excludedSubscriptionIds,
@ApiParam(value = "keep only entries tagged with this tag") @QueryParam("tag") String tag) {
Preconditions.checkNotNull(id);
ReadingMode readType = ReadingMode.all;
ReadingOrder order = ReadingOrder.desc;
int offset = 0;
int limit = 20;
Response response = getCategoryEntries(id, readType, null, offset, limit, order, null, false, null);
Response response = getCategoryEntries(id, readType, newerThan, offset, limit, order, keywords, onlyIds, excludedSubscriptionIds,
tag);
if (response.getStatus() != Status.OK.getStatusCode()) {
return response;
}

View File

@@ -1,17 +1,23 @@
package com.commafeed.frontend.rest.resources;
import java.util.List;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
import com.commafeed.backend.dao.FeedEntryStatusDAO;
import com.commafeed.backend.dao.FeedEntryTagDAO;
import com.commafeed.backend.dao.FeedSubscriptionDAO;
import com.commafeed.backend.services.ApplicationSettingsService;
import com.commafeed.backend.services.FeedEntryService;
import com.commafeed.backend.services.FeedEntryTagService;
import com.commafeed.frontend.model.request.MarkRequest;
import com.commafeed.frontend.model.request.MultipleMarkRequest;
import com.commafeed.frontend.model.request.StarRequest;
import com.commafeed.frontend.model.request.TagRequest;
import com.google.common.base.Preconditions;
import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiOperation;
@@ -30,6 +36,12 @@ public class EntryREST extends AbstractREST {
@Inject
FeedSubscriptionDAO feedSubscriptionDAO;
@Inject
FeedEntryTagDAO feedEntryTagDAO;
@Inject
FeedEntryTagService feedEntryTagService;
@Inject
ApplicationSettingsService applicationSettingsService;
@@ -71,4 +83,24 @@ public class EntryREST extends AbstractREST {
return Response.ok().build();
}
@Path("/tags")
@GET
@ApiOperation(value = "Get list of tags for the user", notes = "Get list of tags for the user")
public Response getTags() {
List<String> tags = feedEntryTagDAO.findByUser(getUser());
return Response.ok(tags).build();
}
@Path("/tag")
@POST
@ApiOperation(value = "Mark a feed entry", notes = "Mark a feed entry as read/unread")
public Response tagFeedEntry(@ApiParam(value = "Tag Request", required = true) TagRequest req) {
Preconditions.checkNotNull(req);
Preconditions.checkNotNull(req.getEntryId());
feedEntryTagService.updateTags(getUser(), req.getEntryId(), req.getTags());
return Response.ok().build();
}
}

View File

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

View File

@@ -74,20 +74,44 @@ public class UserREST extends AbstractREST {
s.setReadingOrder(settings.getReadingOrder().name());
s.setViewMode(settings.getViewMode().name());
s.setShowRead(settings.isShowRead());
s.setSocialButtons(settings.isSocialButtons());
s.setEmail(settings.isEmail());
s.setGmail(settings.isGmail());
s.setFacebook(settings.isFacebook());
s.setTwitter(settings.isTwitter());
s.setGoogleplus(settings.isGoogleplus());
s.setTumblr(settings.isTumblr());
s.setPocket(settings.isPocket());
s.setInstapaper(settings.isInstapaper());
s.setBuffer(settings.isBuffer());
s.setReadability(settings.isReadability());
s.setScrollMarks(settings.isScrollMarks());
s.setTheme(settings.getTheme());
s.setCustomCss(settings.getCustomCss());
s.setLanguage(settings.getLanguage());
s.setScrollSpeed(settings.getScrollSpeed());
} else {
s.setReadingMode(ReadingMode.unread.name());
s.setReadingOrder(ReadingOrder.desc.name());
s.setViewMode(ViewMode.title.name());
s.setShowRead(true);
s.setTheme("default");
s.setSocialButtons(true);
s.setEmail(true);
s.setGmail(true);
s.setFacebook(true);
s.setTwitter(true);
s.setGoogleplus(true);
s.setTumblr(true);
s.setPocket(true);
s.setInstapaper(true);
s.setBuffer(true);
s.setReadability(true);
s.setScrollMarks(true);
s.setLanguage("en");
s.setScrollSpeed(400);
}
return Response.ok(s).build();
}
@@ -114,8 +138,20 @@ public class UserREST extends AbstractREST {
s.setScrollMarks(settings.isScrollMarks());
s.setTheme(settings.getTheme());
s.setCustomCss(settings.getCustomCss());
s.setSocialButtons(settings.isSocialButtons());
s.setLanguage(settings.getLanguage());
s.setScrollSpeed(settings.getScrollSpeed());
s.setEmail(settings.isEmail());
s.setGmail(settings.isGmail());
s.setFacebook(settings.isFacebook());
s.setTwitter(settings.isTwitter());
s.setGoogleplus(settings.isGoogleplus());
s.setTumblr(settings.isTumblr());
s.setPocket(settings.isPocket());
s.setInstapaper(settings.isInstapaper());
s.setBuffer(settings.isBuffer());
s.setReadability(settings.isReadability());
userSettingsDAO.saveOrUpdate(s);
return Response.ok().build();

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.0.xsd">
<changeSet author="athou" id="scroll-speed">
<addColumn tableName="USERSETTINGS">
<column name="scroll_speed" type="BIGINT" />
</addColumn>
</changeSet>
<changeSet author="athou" id="set-default-scroll-speed">
<update tableName="USERSETTINGS">
<column name="scroll_speed" valueNumeric="400" />
</update>
</changeSet>
<changeSet author="athou" id="create-tags-table">
<validCheckSum>7:fdd37bdee09c8fbbcbcd867b05decaae</validCheckSum>
<createTable tableName="FEEDENTRYTAGS">
<column name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true" />
</column>
<column name="entry_id" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="user_id" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="name" type="VARCHAR(40)">
<constraints nullable="false" />
</column>
</createTable>
<addForeignKeyConstraint constraintName="fk_entry_id" baseTableName="FEEDENTRYTAGS" baseColumnNames="entry_id"
referencedTableName="FEEDENTRIES" referencedColumnNames="id" />
<addForeignKeyConstraint constraintName="fk_user_id" baseTableName="FEEDENTRYTAGS" baseColumnNames="user_id"
referencedTableName="USERS" referencedColumnNames="id" />
<createIndex tableName="FEEDENTRYTAGS" indexName="user_entry_name_index">
<column name="user_id" />
<column name="entry_id" />
<column name="name" />
</createIndex>
</changeSet>
<changeSet author="athou" id="add-full-refresh-timestamp">
<addColumn tableName="USERS">
<column name="last_full_refresh" type="DATETIME" />
</addColumn>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<changeSet id="add-detailed-social-options" author="athou">
<addColumn tableName="USERSETTINGS">
<column name="email" type="BIT"></column>
</addColumn>
<addColumn tableName="USERSETTINGS">
<column name="gmail" type="BIT"></column>
</addColumn>
<addColumn tableName="USERSETTINGS">
<column name="facebook" type="BIT"></column>
</addColumn>
<addColumn tableName="USERSETTINGS">
<column name="twitter" type="BIT"></column>
</addColumn>
<addColumn tableName="USERSETTINGS">
<column name="googleplus" type="BIT"></column>
</addColumn>
<addColumn tableName="USERSETTINGS">
<column name="tumblr" type="BIT"></column>
</addColumn>
<addColumn tableName="USERSETTINGS">
<column name="pocket" type="BIT"></column>
</addColumn>
<addColumn tableName="USERSETTINGS">
<column name="instapaper" type="BIT"></column>
</addColumn>
<addColumn tableName="USERSETTINGS">
<column name="buffer" type="BIT"></column>
</addColumn>
<addColumn tableName="USERSETTINGS">
<column name="readability" type="BIT"></column>
</addColumn>
<dropColumn tableName="USERSETTINGS" columnName="socialButtons" />
<update tableName="USERSETTINGS">
<column name="email" valueBoolean="true"></column>
<column name="gmail" valueBoolean="true"></column>
<column name="facebook" valueBoolean="true"></column>
<column name="twitter" valueBoolean="true"></column>
<column name="googleplus" valueBoolean="true"></column>
<column name="tumblr" valueBoolean="true"></column>
<column name="pocket" valueBoolean="true"></column>
<column name="instapaper" valueBoolean="true"></column>
<column name="buffer" valueBoolean="true"></column>
<column name="readability" valueBoolean="true"></column>
</update>
</changeSet>
</databaseChangeLog>

View File

@@ -7,5 +7,7 @@
<include file="changelogs/db.changelog-1.1.xml" />
<include file="changelogs/db.changelog-1.2.xml" />
<include file="changelogs/db.changelog-1.3.xml" />
<include file="changelogs/db.changelog-1.4.xml" />
<include file="changelogs/db.changelog-1.5.xml" />
</databaseChangeLog>

View File

@@ -6,6 +6,7 @@ global.download=تحميل
global.link=رابط
global.bookmark=مرجعية
global.close=أغلق
global.tags=Tags ####### Needs translation
tree.subscribe=اشترك
tree.import=استورد
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=الترتيب حسب التاريخ تصاعدي / ت
toolbar.titles_only=العناوين فقط
toolbar.expanded_view=عرض موسع
toolbar.mark_all_as_read=اعتبر الكل مقروء
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=العناصر الأقدم من يوم
toolbar.mark_all_older_week=العناصر الأقدم من أسبوع
toolbar.mark_all_older_two_weeks=العناصر الأقدم من أسبوعين
@@ -66,6 +68,8 @@ settings.general.show_unread=Show feeds and categories with no unread entries
settings.general.social_buttons=Show social sharing buttons
settings.general.scroll_marks=In expanded view, scrolling through entries mark them as read
settings.appearance=Appearance
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=Theme
settings.submit_your_theme=Submit your theme
settings.custom_css=Custom CSS
@@ -83,7 +87,10 @@ details.queued_for_refresh=Queued for refresh
details.feed_url=Feed URL
details.generate_api_key_first=Generate an API key in your profile first.
details.unsubscribe=Unsubscribe
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
details.category_details=Category details
details.tag_details=Tag details ####### Needs translation
details.parent_category=Parent category
profile.user_name=User name
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Generate new API key
profile.generate_new_api_key_info=Changing password will generate a new API key
profile.opml_export=OPML export
profile.delete_account=Delete account
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=REST API
about.keyboard_shortcuts=Keyboard shortcuts

View File

@@ -0,0 +1,156 @@
global.save=Desa
global.cancel=Cancel·la
global.delete=Esborra
global.required=Requerit
global.download=Descarrega
global.link=Enllaç
global.bookmark=Adreça d'interès
global.close=Tancar
global.tags=Etiquetes
tree.subscribe=Subscriure
tree.import=Importa
tree.new_category=Nova categoria
tree.all=Tot
tree.starred=Destacats
subscribe.feed_url=URL del canal
subscribe.feed_name=Nom del canal
subscribe.category=Categoria
import.google_reader_prefix=Importaré els canals del teu
import.google_reader_suffix= compte.
import.google_download=O be, carrega el teu fitxer subscriptions.xml.
import.google_download_link=Descarrega'l d'aquí.
import.xml_file=Fitxer OPML
new_category.name=Nom
new_category.parent=Arrel
toolbar.unread=Per llegir
toolbar.all=Tots
toolbar.previous_entry=Entrada prèvia
toolbar.next_entry=Entrada següent
toolbar.refresh=Actualitzar
toolbar.refresh_all=Força l'actualització de tots els canals
toolbar.sort_by_asc_desc=Ordenar per data asc/desc
toolbar.titles_only=Només títols
toolbar.expanded_view=Vista ampliada
toolbar.mark_all_as_read=Marcar tots llegits
toolbar.mark_all_older_12_hours=Ítems més vells de 12 hores
toolbar.mark_all_older_day=Ítems més vells d'un dia
toolbar.mark_all_older_week=Ítems més vells d'una setmana
toolbar.mark_all_older_two_weeks=Ítems més vells de dues setmanes
toolbar.settings=Configuració
toolbar.profile=Perfil
toolbar.admin=Admin
toolbar.about=Quant a
toolbar.logout=Desconnecta't
toolbar.donate=Donació
view.entry_source=de
view.entry_author=per
view.error_while_loading_feed=Error carregant el canal
view.keep_unread=Conserva com a no llegit
view.no_unread_items=no té ítems sense llegir.
view.mark_up_to_here=Marcar com a llegit fins aquí
view.search_for=cercant:
view.no_search_results=No hi ha coincidències per les paraules clau sol·licitades
feedsearch.hint=Introdueix una subscripció...
feedsearch.help=Utilitza la tecla de retorn per seleccionar i les tecles de cursor per navegar.
feedsearch.result_prefix=Les teves subscripcions:
settings.general=General
settings.general.language=Idioma
settings.general.language.contribute=Contribueix amb traduccions
settings.general.show_unread=Mostrar canals i categories amb entrades sense llegir
settings.general.social_buttons=Mostrar botons per compartir en xarxes socials
settings.general.scroll_marks=A la vista ampliada si et desplaces per les entrades les marques com a llegides
settings.appearance=Aparença
settings.scroll_speed=Velocitat de desplaçament quan navegues entre entrades (en mil·lisegons)
settings.scroll_speed.help=Fixa a 0 per desactivar
settings.theme=Tema
settings.submit_your_theme=Envia un tema
settings.custom_css=CSS personalitzat
details.feed_details=Detalls del canal
details.url=URL
details.website=Lloc web
details.name=Nom
details.category=Categoria
details.position=Posició
details.last_refresh=Darrera actualització
details.message=Darrer missatge d'actualització
details.next_refresh=Propera actualització
details.queued_for_refresh=A la cua d'actualització
details.feed_url=URL del canal
details.generate_api_key_first=Abans cal que generis una clau API en el teu perfil.
details.unsubscribe=Cancel·la la subscripció
details.unsubscribe_confirmation=Segur que vols cancel·lar la subscripció del canal?
details.delete_category_confirmation=Segur que vols esborrar la categoria?
details.category_details=Detalls de la categoria
details.tag_details=Detalls de l'etiqueta
details.parent_category=Categoria arrel
profile.user_name=Nom d'usuari
profile.email=Adreça electrònica
profile.change_password=Canvia la contrasenya
profile.confirm_password=Confirma la contrasenya
profile.minimum_6_chars=Mínim de 6 caracters
profile.passwords_do_not_match=Les contrasenyes no coincideixen
profile.api_key=Clau API
profile.api_key_not_generated=Encara no s'ha generat
profile.generate_new_api_key=Genera una nova clau API
profile.generate_new_api_key_info=El canvi de contrasenya generarà una nova clau API
profile.opml_export=Exporta OPML
profile.delete_account=Esborra el compte
profile.delete_account_confirmation=Vols esborrar el teu compte? No ho podràs desfer!
about.rest_api=REST API
about.keyboard_shortcuts=Dreceres de teclat
about.version=Versió de CommaFeed
about.line1_prefix=CommaFeed és un projecte de codi font obert. El codi font és hostatjat a
about.line1_suffix=.
about.line2_prefix=Si trobes un problema, si us plau informa'n a la pàgina de problemes del
about.line2_suffix=\ projecte.
about.line3=Si t'agrada el projecte, pensa en fer un donatiu per recolzar el desenvolupador i per ajudar amb les despeses de l'hostatge del lloc web.
about.line4=I pels que preferiu bitcoin, aquí teniu l'adreça
about.goodies=Afegitons
about.goodies.android_app=App Android
about.goodies.subscribe_url=URL de subscripció
about.goodies.chrome_extension=Extensió del Chrome
about.goodies.firefox_extension=Extensió del Firefox
about.goodies.opera_extension=Extensió de l'Opera
about.goodies.subscribe_bookmarklet=Afegeix bookmarklet de subscripció (clica)
about.goodies.subscribe_bookmarklet_asc=Primer els vells
about.goodies.subscribe_bookmarklet_desc=Primer els nous
about.goodies.next_unread_bookmarklet=Bookmarklet del proper ítem sense llegir (arrosega a la barra d'adreces d'interès)
about.translation=Traducció
about.translation.message=Necessitem la teva ajuda per traduir CommaFeed.
about.translation.link=Informació per contribuir amb traduccions.
about.announcements=Anuncis
about.rest_api.line1=CommaFeed funciona amb JAX-RS i AngularJS. Per tant, té disponible una API REST.
about.rest_api.link_to_documentation=Enllaç a la documentació.
about.shortcuts.mouse_middleclick=Clic amb el botó del mig
about.shortcuts.open_next_entry=obrir entrada següent
about.shortcuts.open_previous_entry=obrir entrada prèvia
about.shortcuts.spacebar=espai/majúscula+espai
about.shortcuts.move_page_down_up=mou la pàgina avall/amunt
about.shortcuts.focus_next_entry=fixa el focus en l'entrada següent entrada sense obrir-la
about.shortcuts.focus_previous_entry=fixa el focus en l'entrada prèvia sense obrir-la
about.shortcuts.open_next_feed=obrir canal o categoria següent
about.shortcuts.open_previous_feed=obrir canal o categoria prèvia
about.shortcuts.open_close_current_entry=obre/tanca entrada actual
about.shortcuts.open_current_entry_in_new_window=obrir entrada actual en una finestra nova
about.shortcuts.open_current_entry_in_new_window_background=obrir entrada actual en una finestra nova en segon pla
about.shortcuts.star_unstar=destacar/treure destacat a l'entrada actual
about.shortcuts.mark_current_entry=marcar com a llegida/no llegida l'entrada actual
about.shortcuts.mark_all_as_read=marcar totes les entrades com a llegides
about.shortcuts.open_in_new_tab_mark_as_read=obrir entrada en una pestanya nova i marcar com a llegida
about.shortcuts.fullscreen=commutar el mode de pantalla completa
about.shortcuts.font_size=incrementar/reduir la mida de la font de l'entrada actual
about.shortcuts.go_to_all=anar a la vista de Tot
about.shortcuts.go_to_starred=anar a la vista de Destacats
about.shortcuts.feed_search=navegar a una subscripció introduint-ne el nom

View File

@@ -6,6 +6,7 @@ global.download = Stáhnout
global.link = Odkaz
global.bookmark = Záložky
global.close = Zavřít
global.tags=Tags ####### Needs translation
tree.subscribe = Nový odběr
tree.import = Importovat
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc = Seřadit podle nejnovějšího/nejstaršího
toolbar.titles_only = Zobrazit jenom titulky
toolbar.expanded_view = Rozšířený náhled
toolbar.mark_all_as_read = Označit vše jako přečtené
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day = Položky starší než den
toolbar.mark_all_older_week = Položky starší než týden
toolbar.mark_all_older_two_weeks = Položky starší než dva týdny
@@ -66,6 +68,8 @@ settings.general.show_unread = Zobrazit položky a kategorie z přečtenými pol
settings.general.social_buttons = Zobrazit možnosti sdílení
settings.general.scroll_marks = Skrolování v rozšířeném náhledu označí položky jako přečtené
settings.appearance = Vzhled
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme = Motiv
settings.submit_your_theme = Nahrát vlastní motiv
settings.custom_css = Vlastní motiv (CSS)
@@ -83,7 +87,10 @@ details.queued_for_refresh = Ve frontě na obnovu
details.feed_url = URL RSS zdroje
details.generate_api_key_first = Vygenerujte si API klíč na stránce vašeho profilu.
details.unsubscribe = Odhlásit odběr.
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
details.category_details = Detail kategorie
details.tag_details=Tag details ####### Needs translation
details.parent_category = Hlavní kategorie
profile.user_name = Uživatelské jméno
@@ -98,6 +105,7 @@ profile.generate_new_api_key = Vygenerovat nový API klíč
profile.generate_new_api_key_info = Změnou hesla vygenerujete nový API klíč
profile.opml_export = exportovat do formátu OPML
profile.delete_account = Odstranit účet
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api = REST API
about.keyboard_shortcuts = Klávesové zkratky

View File

@@ -6,6 +6,7 @@ global.download=Lawrlwytho
global.link=Dolen
global.bookmark=Nod tudalen
global.close=Cau
global.tags=Tags ####### Needs translation
tree.subscribe=Tanysgrifio
tree.import=Mewnforio
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Trefnu yn ôl dyddiad
toolbar.titles_only=Teitlau yn unig
toolbar.expanded_view=Golwg estynedig
toolbar.mark_all_as_read=Nodi'r cyfan fel wedi ei ddarllen
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=Eitemau hyn na diwrnod
toolbar.mark_all_older_week=Eitemau hyn nag wythnos
toolbar.mark_all_older_two_weeks=Eitemau hyn na phythefnos
@@ -66,6 +68,8 @@ settings.general.show_unread=Dangos ffrydiau a chategoriau gyda dim eitemau heb
settings.general.social_buttons=Dangos botymau rhannu
settings.general.scroll_marks=Marcio eitemau fel wedi eu darllen wrth sgrolio drwyddynt yn y golwg estynedig ###### Defnyddio gystrawen debyg i'r ddau uwch.
settings.appearance=Golwg
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=Thema
settings.submit_your_theme=Cyflwyna dy thema
settings.custom_css=CSS wedi'i addasu
@@ -83,7 +87,10 @@ details.queued_for_refresh=Ciwiwyd i'w adnewyddu
details.feed_url=URL Ffrwd
details.generate_api_key_first=Rhaid creu allwedd API yn dy broffil yn gyntaf.
details.unsubscribe=Dad-danysgrifio
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
details.category_details=Manylion categori
details.tag_details=Tag details ####### Needs translation
details.parent_category=Categori rhiant
profile.user_name=Enw defnyddiwr
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Creu allwedd API newydd
profile.generate_new_api_key_info=Mae newid cyfrinair yn creu allwedd API newydd
profile.opml_export=Allforio OPML
profile.delete_account=Dileu cyfrif
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=REST API
about.keyboard_shortcuts=Llwybr byr bysellfwrdd

View File

@@ -6,6 +6,7 @@ global.download=Hent
global.link=Link
global.bookmark=Bogmærke
global.close=Luk
global.tags=Tags ####### Needs translation
tree.subscribe=Abonner
tree.import=Importer
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Sorter efter dato ny/gammel
toolbar.titles_only=Kun titler
toolbar.expanded_view=Udvidet visning
toolbar.mark_all_as_read=Marker alle som læst
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=Artikler ældere end én dag
toolbar.mark_all_older_week=Artikler ældere end én uge
toolbar.mark_all_older_two_weeks=Artikler ældere end to uger
@@ -66,6 +68,8 @@ settings.general.show_unread=Vis abonnomenter og kategorier med læste artikler
settings.general.social_buttons=Vis delingsknapper
settings.general.scroll_marks=I udvidet visning, marker artikler som læste når der rulles forbi dem
settings.appearance=Udseende
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=Tema
settings.submit_your_theme=Indsend dit tema
settings.custom_css=Brugerdefineret CSS
@@ -83,7 +87,10 @@ details.queued_for_refresh=I kø til opdatering
details.feed_url=URL for abonnement
details.generate_api_key_first=Generer en API nøgle i din profil først.
details.unsubscribe=Afmeld abonnement
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
details.category_details=Kategori detaljer
details.tag_details=Tag details ####### Needs translation
details.parent_category=Overordnet kategori
profile.user_name=Brugernavn
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Generer ny API nøgle
profile.generate_new_api_key_info=Ændring af adgangskode vil generere en ny API nøgle
profile.opml_export=OPML eksport
profile.delete_account=Slet konto
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=REST API
about.keyboard_shortcuts=Tastaturgenveje

View File

@@ -6,6 +6,7 @@ global.download=Herunterladen
global.link=Link
global.bookmark=Lesezeichen
global.close=Schließen
global.tags=Tags
tree.subscribe=Abonnieren
tree.import=Importieren
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Nach Datum sortieren (auf-/absteigend)
toolbar.titles_only=Nur Überschriften
toolbar.expanded_view=Ausgedehnte Ansicht
toolbar.mark_all_as_read=Alle Artikel als gelesen markieren
toolbar.mark_all_older_12_hours=Artikel älter als 12 Stunden
toolbar.mark_all_older_day=Artikel älter als ein Tag
toolbar.mark_all_older_week=Artikel älter als eine Woche
toolbar.mark_all_older_two_weeks=Artikel älter als zwei Wochen
@@ -66,6 +68,8 @@ settings.general.show_unread=Zeige Feeds und Kategorien mit ungelesenen Einträg
settings.general.social_buttons=Zeige Buttons zum Teilen von Inhalten über soziale Netzwerke
settings.general.scroll_marks=In der ausgedehnten Ansicht werden Artikel beim Scrollen als gelesen markiert
settings.appearance=Aussehen
settings.scroll_speed=Geschwindigkeit beim scrollen zwischen Einträgen (in Millisekunden)
settings.scroll_speed.help=setze auf 0 zum deaktivieren
settings.theme=Theme
settings.submit_your_theme=Füg dein Theme hinzu
settings.custom_css=Eigenes CSS
@@ -77,13 +81,16 @@ details.name=Name
details.category=Kategorie
details.position=Position
details.last_refresh=Letzte Aktualisierung
details.message=Last refresh message ####### Needs translation
details.message=Nachricht der letzten Aktualisierung
details.next_refresh=Nächste Aktualisierung
details.queued_for_refresh=Wartet auf Aktualisierung
details.feed_url=Feed Adresse
details.generate_api_key_first=Generiere zuerst einen API Schlüssel in deinem Profil.
details.unsubscribe=Kündigen
details.unsubscribe_confirmation=Bist du sicher das du diesen Feed kündigen möchtest?
details.delete_category_confirmation=Bist du sicher das du diese Kategorie löschen möchtest?
details.category_details=Kategoriedetails
details.tag_details=Tag Details
details.parent_category=Übergeordnete Kategorie
profile.user_name=Benutzername
@@ -97,7 +104,8 @@ profile.api_key_not_generated=Noch nicht generiert
profile.generate_new_api_key=Generiere einen neuen API key
profile.generate_new_api_key_info=Das Ändern des Passwortes erzeugt einen neuen API Schlüssel
profile.opml_export=OPML exportieren
profile.delete_account=Lösche den Account
profile.delete_account=Account löschen
profile.delete_account_confirmation=Deinen Account löschen? Es gibt kein Zurück!
about.rest_api=REST API
about.keyboard_shortcuts=Tastatur Kurzbefehle

View File

@@ -6,6 +6,7 @@ global.download=Download
global.link=Link
global.bookmark=Bookmark
global.close=Close
global.tags=Tags
tree.subscribe=Subscribe
tree.import=Import
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Sort by date asc/desc
toolbar.titles_only=Titles only
toolbar.expanded_view=Expanded view
toolbar.mark_all_as_read=Mark all as read
toolbar.mark_all_older_12_hours=Items older than 12 hours
toolbar.mark_all_older_day=Items older than a day
toolbar.mark_all_older_week=Items older than a week
toolbar.mark_all_older_two_weeks=Items older than two weeks
@@ -66,6 +68,8 @@ settings.general.show_unread=Show feeds and categories with no unread entries
settings.general.social_buttons=Show social sharing buttons
settings.general.scroll_marks=In expanded view, scrolling through entries mark them as read
settings.appearance=Appearance
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds)
settings.scroll_speed.help=set to 0 to disable
settings.theme=Theme
settings.submit_your_theme=Submit your theme
settings.custom_css=Custom CSS
@@ -83,7 +87,10 @@ details.queued_for_refresh=Queued for refresh
details.feed_url=Feed URL
details.generate_api_key_first=Generate an API key in your profile first.
details.unsubscribe=Unsubscribe
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed?
details.delete_category_confirmation=Are you sure you want to delete this category?
details.category_details=Category details
details.tag_details=Tag details
details.parent_category=Parent category
profile.user_name=User name
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Generate new API key
profile.generate_new_api_key_info=Changing password will generate a new API key
profile.opml_export=OPML export
profile.delete_account=Delete account
profile.delete_account_confirmation=Delete your account? There's no turning back!
about.rest_api=REST API
about.keyboard_shortcuts=Keyboard shortcuts
@@ -145,4 +153,4 @@ about.shortcuts.fullscreen=toggle full screen mode
about.shortcuts.font_size=increase/decrease font size of the current entry
about.shortcuts.go_to_all=go to the All view
about.shortcuts.go_to_starred=go to the Starred view
about.shortcuts.feed_search=navigate to a subscription by entering the subscription name
about.shortcuts.feed_search=navigate to a subscription by entering the subscription name

View File

@@ -6,6 +6,7 @@ global.download=Descargar
global.link=Enlace
global.bookmark=Marcador
global.close=Close ####### Needs translation
global.tags=Tags ####### Needs translation
tree.subscribe=Subscribir
tree.import=Importar
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Ordenar por fecha asc/desc
toolbar.titles_only=Sólo Títulos
toolbar.expanded_view=Vista Expandida
toolbar.mark_all_as_read=Marcar todos como leído
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=Artículos anteriores a un día
toolbar.mark_all_older_week=Artículos más de una semana
toolbar.mark_all_older_two_weeks=Artículos más de does semanas
@@ -66,6 +68,8 @@ settings.general.show_unread=Mostrar canales y categorías sin entradas no leíd
settings.general.social_buttons=Mostrar botones de compartir de redes sociales.
settings.general.scroll_marks=En vista expandida, el desplazamiento por las entradas las marca como leídas
settings.appearance=Appearance ####### Needs translation
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=Theme ####### Needs translation
settings.submit_your_theme=Submit your theme ####### Needs translation
settings.custom_css=CSS Personalizado
@@ -83,7 +87,10 @@ details.queued_for_refresh=Queued for refresh ####### Needs translation
details.feed_url=URL del Canal
details.generate_api_key_first=Genera una llave API en tu perfil primero.
details.unsubscribe=Terminar subscripción
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
details.category_details=Detalles de la categoría
details.tag_details=Tag details ####### Needs translation
details.parent_category=Categoría principal
profile.user_name=Nombre de usuario
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Generar nueva llave API
profile.generate_new_api_key_info=Al cambiar la contraseña se generará una nueva llave API
profile.opml_export=Exportación de OPML
profile.delete_account=Eliminar cuenta
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=REST API
about.keyboard_shortcuts=Atajos de teclado

View File

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

View File

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

View File

@@ -6,6 +6,7 @@ global.download=Télécharger
global.link=Lien
global.bookmark=Favoris
global.close=Fermer
global.tags=Tags ####### Needs translation
tree.subscribe=S'abonner
tree.import=Importer
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Trier par date croissante/décroissante
toolbar.titles_only=Titres uniquement
toolbar.expanded_view=Vue étendue
toolbar.mark_all_as_read=Tout marquer comme lu
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=Articles de plus d'un jour
toolbar.mark_all_older_week=Articles de plus d'une semaine
toolbar.mark_all_older_two_weeks=Articles de plus d'un mois
@@ -66,6 +68,8 @@ settings.general.show_unread=Afficher les flux et les catégories pour lesquels
settings.general.social_buttons=Afficher les boutons de partage sur réseaux sociaux
settings.general.scroll_marks=En mode de lecture étendu, marquer comme lu les éléments lorsque la fenêtre descend.
settings.appearance=Apparence
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=Thème
settings.submit_your_theme=Soumettez votre thème.
settings.custom_css=CSS personnelle
@@ -83,7 +87,10 @@ details.queued_for_refresh=En file d'attente
details.feed_url=URL du flux
details.generate_api_key_first=Générez une clé API dans votre profil d'abord.
details.unsubscribe=Se désabonner
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
details.category_details=Détails de la catégorie
details.tag_details=Tag details ####### Needs translation
details.parent_category=Catégorie parente
profile.user_name=Nom
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Générer une nouvelle clé API
profile.generate_new_api_key_info=Changer de mot de passe va générer une nouvelle clé API
profile.opml_export=Export du fichier OPML
profile.delete_account=Effacer le compte
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=API REST
about.keyboard_shortcuts=Raccourcis clavier

View File

@@ -6,6 +6,7 @@ global.download=Descargar
global.link=Ligazón
global.bookmark=Marcador
global.close=Pechar
global.tags=Tags ####### Needs translation
tree.subscribe=Subscribir
tree.import=Importar
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Ordenar por data asc/desc
toolbar.titles_only=Só títulos
toolbar.expanded_view=Vista expandida
toolbar.mark_all_as_read=Marcar todos como lidos
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=Artigos anteriores a un día
toolbar.mark_all_older_week=Artigos de máis de unha semana
toolbar.mark_all_older_two_weeks=Artigos de máis de dúas semanas
@@ -66,6 +68,8 @@ settings.general.show_unread=Mostrar fontes e categorías sen entradas non lidas
settings.general.social_buttons=Mostrar botóns de compartir en redes sociais.
settings.general.scroll_marks=En vista expandida, o desplazamento polas entradas márcaas como lidas.
settings.appearance=Aspecto
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=Decorado
settings.submit_your_theme=Envíe o seu decorado
settings.custom_css=CSS Personalizado
@@ -83,7 +87,10 @@ details.queued_for_refresh=En cola para actualizar
details.feed_url=URL da fonte
details.generate_api_key_first=Antes debes xerar unha chave API no teu perfil.
details.unsubscribe=Rematar suscripción
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
details.category_details=Detalles da categoría
details.tag_details=Tag details ####### Needs translation
details.parent_category=Categoría principal
profile.user_name=Nome de usuario
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Xerar nova chave da API
profile.generate_new_api_key_info=Ao cambiar o contrasinal xerarase unha nova chave API
profile.opml_export=Exportación de OPML
profile.delete_account=Eliminar conta
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=REST API
about.keyboard_shortcuts=Atallos de teclado

View File

@@ -6,6 +6,7 @@ global.download=جیرأکش
global.link=خال
global.bookmark=بوکمارک
global.close=دَوَستن
global.tags=Tags ####### Needs translation
tree.subscribe=مشترک ببید
tree.import=درینأدأن
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=تاریخˇ سر دچئن
toolbar.titles_only=خالی تیتران
toolbar.expanded_view=واشاده نما
toolbar.mark_all_as_read=همه‌ته مطالبه چاکون بخانده
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=یک روز پیشترˇ مطالب
toolbar.mark_all_older_week=یک هفته پیشترˇ مطالب
toolbar.mark_all_older_two_weeks=چن هفته پیشترˇ مطالب
@@ -66,6 +68,8 @@ settings.general.show_unread=تنها خوراک‌ها و دسته‌های ر
settings.general.social_buttons=نشان‌دادن دکمه‌های اشتراک‌گذاری در شبکه‌های اجتماعی
settings.general.scroll_marks=در نمای گسترش‌یافته، لغزیدن بر روی مطالب به‌عنوان نشانه‌گذاری به‌عنوان خوانده‌شده در نظر گرفته‌شوند.
settings.appearance=ظاهر
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=پوسته
settings.submit_your_theme=شیمه پوستهٰ اوسه کونید
settings.custom_css=سی‌اس‌اس شخصی‌سازی‌شده
@@ -83,7 +87,10 @@ details.queued_for_refresh=منتظر برای بروزرسانی
details.feed_url=نشانی خوراک
details.generate_api_key_first=ابتدا یک کلید API در نمایهٔ خود ایجاد کنید.
details.unsubscribe=لغو اشتراک
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
details.category_details=جرگه جزئیات
details.tag_details=Tag details ####### Needs translation
details.parent_category=پئرˇ جرگه
profile.user_name=کاربری نام
@@ -98,6 +105,7 @@ profile.generate_new_api_key=تازه کلید چاگودن API
profile.generate_new_api_key_info=رمزه عوضأگودن API کلیده چاکونه.
profile.opml_export=برینأدأن OPML
profile.delete_account=کاربری حسابه پاکودن
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=REST API
about.keyboard_shortcuts=وئر زئنˇ کلیدان

View File

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

View File

@@ -6,6 +6,7 @@ global.download=Download
global.link=Link
global.bookmark=Segnalibro
global.close=Chiudi
global.tags=Tags ####### Needs translation
tree.subscribe=Iscriviti
tree.import=Importa
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Sort by date asc/desc
toolbar.titles_only=Solo titoli
toolbar.expanded_view=Espandi
toolbar.mark_all_as_read=Contrassegna tutto come già letto
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=Elementi più vecchi di un giorno
toolbar.mark_all_older_week=Elementi più vecchi di una settimana
toolbar.mark_all_older_two_weeks=Elementi più vecchi di due settimane
@@ -66,6 +68,8 @@ settings.general.show_unread=Show feeds and categories with no unread entries
settings.general.social_buttons=Visualizza i social button
settings.general.scroll_marks=Marca come letto quando scorri
settings.appearance=Appearance ####### Needs translation
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=Tema
settings.submit_your_theme=Sottoponi il tuo tema
settings.custom_css=Css modificato
@@ -83,7 +87,10 @@ details.queued_for_refresh=In attesa per l'aggiornamento
details.feed_url=Feed URL
details.generate_api_key_first=Generate an API key in your profile first.
details.unsubscribe=Annulla l"'"iscrizione
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
details.category_details=Dettagli categoria
details.tag_details=Tag details ####### Needs translation
details.parent_category=Parent category
profile.user_name=User name
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Genera una nuova chiave API
profile.generate_new_api_key_info=Cambiando la password sarà generata una nuova chiave API
profile.opml_export=Esporta OPML
profile.delete_account=Elimina account
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=REST API
about.keyboard_shortcuts=Scorciatoie da tastiera

View File

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

View File

@@ -6,6 +6,7 @@ global.download=Download ####### Needs translation
global.link=Link ####### Needs translation
global.bookmark=Bookmark ####### Needs translation
global.close=Close ####### Needs translation
global.tags=Tags ####### Needs translation
tree.subscribe=구독
tree.import=임포트
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Sort by date asc/desc ####### Needs translation
toolbar.titles_only=Titles only ####### Needs translation
toolbar.expanded_view=Expanded view ####### Needs translation
toolbar.mark_all_as_read=읽음표시
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=Items older than a day ####### Needs translation
toolbar.mark_all_older_week=Items older than a week ####### Needs translation
toolbar.mark_all_older_two_weeks=Items older than two weeks ####### Needs translation
@@ -66,6 +68,8 @@ settings.general.show_unread=안읽은 항목들이 있는 피드와 카테고
settings.general.social_buttons=소셜미디아 버튼들 보여주기
settings.general.scroll_marks=Expanded View에서 스크롤하면 항목들을 읽음으로 저장하기
settings.appearance=Appearance ####### Needs translation
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=Theme ####### Needs translation
settings.submit_your_theme=Submit your theme ####### Needs translation
settings.custom_css=커스톰 CSS
@@ -83,7 +87,10 @@ details.queued_for_refresh=Queued for refresh ####### Needs translation
details.feed_url=피드 유알엘
details.generate_api_key_first=당신의 프로필을 위해 API Key를 먼저 생성하세요.
details.unsubscribe=주소 삭제
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
details.category_details=카테고리 세부
details.tag_details=Tag details ####### Needs translation
details.parent_category=부모 카테고리
profile.user_name=사용자 이름
@@ -98,6 +105,7 @@ profile.generate_new_api_key=API Key 생성하기
profile.generate_new_api_key_info=비밀번호를 변경하면 새로운 API Key가 생성됩니다.
profile.opml_export=OPML export ####### Needs translation
profile.delete_account=프로필삭제
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=REST API
about.keyboard_shortcuts=단축기

View File

@@ -1,4 +1,5 @@
ar=العربية
ca=Català
en=English
es=Español
de=Deutsch
@@ -7,6 +8,7 @@ fr=Français
gl=Galician
glk=گیلکی
hu=Magyar
ja=日本語
ko=한국어
nl=Nederlands
nb=Norsk (bokmål)

View File

@@ -6,6 +6,7 @@ global.download=Muat turun
global.link=Pautan
global.bookmark=Bookmark
global.close=Tutup
global.tags=Tags ####### Needs translation
tree.subscribe=Langgan
tree.import=Import
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Aturkan mengikut tarikh (baru/lama)
toolbar.titles_only=Tajuk sahaja
toolbar.expanded_view=Wide view
toolbar.mark_all_as_read=Tanda kesemuanya telah dibaca
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=Lebih lama daripada sehari
toolbar.mark_all_older_week=Lebih lama daripada seminggu
toolbar.mark_all_older_two_weeks=Lebih lama daripada dua minggu
@@ -66,6 +68,8 @@ settings.general.show_unread=Tunjuk semua feed dan kategori yang telah dibaca
settings.general.social_buttons=Tunjuk social sharing
settings.general.scroll_marks=Dalam wide view, tanda item dibaca ketika scrolling
settings.appearance=Rupa
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=Tema
settings.submit_your_theme=Muat naik tema anda
settings.custom_css=Custom CSS
@@ -83,7 +87,10 @@ details.queued_for_refresh=Diaturkan untuk refresh
details.feed_url=URL Feed
details.generate_api_key_first=Janakan API key dalam profil anda dahulu.
details.unsubscribe=Hentikan langganan
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
details.category_details=Butir-butir kategori
details.tag_details=Tag details ####### Needs translation
details.parent_category=Kategori induk
profile.user_name=User name
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Jana API key baru
profile.generate_new_api_key_info=Pertukaran kata laluan akan menjanakan API key yang baru
profile.opml_export=Export OPML
profile.delete_account=Padam akaun
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=REST API
about.keyboard_shortcuts=Pintasan papan kekunci

View File

@@ -6,6 +6,7 @@ global.download=Last ned
global.link=Lenke
global.bookmark=Bokmerke
global.close=Lukk
global.tags=Tags ####### Needs translation
tree.subscribe=Abonner
tree.import=Importer
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Sorter etter dato ny/gammel
toolbar.titles_only=Kun titler
toolbar.expanded_view=Utvidet visning
toolbar.mark_all_as_read=Merk alle som lest
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=Artikler eldre enn én dag
toolbar.mark_all_older_week=Artikler eldre enn én uke
toolbar.mark_all_older_two_weeks=Artikler eldre enn to uker
@@ -66,6 +68,8 @@ settings.general.show_unread=Vis abonnementer og kategorier uten nye artikler
settings.general.social_buttons=Vis delingsknapper
settings.general.scroll_marks=I utvidet visning, merk artikler som leste når du blar deg forbi dem.
settings.appearance=Utseende
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=Drakt
settings.submit_your_theme=Legg til egen drakt
settings.custom_css=Egendefinert CSS
@@ -83,7 +87,10 @@ details.queued_for_refresh=I kø for oppdatering
details.feed_url=URL for abonnement
details.generate_api_key_first=Generer en API-nøkkel under profilinnstillinger først.
details.unsubscribe=Avslutt abonnement
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
details.category_details=Kategoridetaljer
details.tag_details=Tag details ####### Needs translation
details.parent_category=Overordnet kategori
profile.user_name=Brukernavn
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Generer ny API-nøkkel
profile.generate_new_api_key_info=Endring av passord vil generere en ny API-nøkkel
profile.opml_export=OPML-eksport
profile.delete_account=Slett bruker
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=REST API
about.keyboard_shortcuts=Hurtigtaster

View File

@@ -6,6 +6,7 @@ global.download=Download
global.link=Link
global.bookmark=Bookmark
global.close=Sluiten
global.tags=Tags ####### Needs translation
tree.subscribe=Abonneer
tree.import=Importeer
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Sorteer op datum opl/afl
toolbar.titles_only=Alleen titels
toolbar.expanded_view=Uitgebreide weergave
toolbar.mark_all_as_read=Markeer alles als gelezen
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=Artikelen ouder dan een dag
toolbar.mark_all_older_week=Artikelen ouder dan een week
toolbar.mark_all_older_two_weeks=Artikelen ouder dan twee weken
@@ -66,6 +68,8 @@ settings.general.show_unread=Laat feeds en categorieën zonder ongelezen artikel
settings.general.social_buttons=Laat Social Media knoppen zien
settings.general.scroll_marks=Markeer artikelen als gelezen, wanneer je er doorheen scrollt
settings.appearance=Uiterlijk
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=Thema
settings.submit_your_theme=Stuur thema in
settings.custom_css=Custom CSS
@@ -83,7 +87,10 @@ details.queued_for_refresh=In wachtrij voor vernieuwing
details.feed_url=Feed URL
details.generate_api_key_first=Genereer eerst een API sleutel in je profiel.
details.unsubscribe=Abonnement opzeggen
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
details.category_details=Categorie details
details.tag_details=Tag details ####### Needs translation
details.parent_category=Bovenliggende categorie
profile.user_name=Gebruikersnaam
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Genereer nieuwe API sleutel
profile.generate_new_api_key_info=Het veranderen van het wachtwoord genereert een nieuwe API sleutel
profile.opml_export=OPML export
profile.delete_account=Verwijder account
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=REST API
about.keyboard_shortcuts=Sneltoetsen

View File

@@ -6,6 +6,7 @@ global.download=Last ned
global.link=Lenkje
global.bookmark=Bokmerke
global.close=Lukk
global.tags=Tags ####### Needs translation
tree.subscribe=Abonner
tree.import=Importer
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Sorter etter dato ny/gamal
toolbar.titles_only=Berre titlar
toolbar.expanded_view=Utvida visning
toolbar.mark_all_as_read=Merk alle som lesne
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=Artiklar eldre enn éin dag
toolbar.mark_all_older_week=Artiklar eldre enn éi veke
toolbar.mark_all_older_two_weeks=Artiklar eldre enn to veker
@@ -66,6 +68,8 @@ settings.general.show_unread=Vis abonnement og kategoriar utan nye artiklar
settings.general.social_buttons=Vis delingsknappar
settings.general.scroll_marks=I utvida visning, merk artiklar som lesne når du blar deg forbi dei.
settings.appearance=Utsjånad
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=Drakt
settings.submit_your_theme=Legg til eiga drakt
settings.custom_css=Skreddarsydd CSS
@@ -83,7 +87,10 @@ details.queued_for_refresh=I kø for oppdatering
details.feed_url=URL for abonnement
details.generate_api_key_first=Generer ein API-nykel under profilinnstillingar fyrst.
details.unsubscribe=Avslutt abonnement
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
details.category_details=Kategoridetaljar
details.tag_details=Tag details ####### Needs translation
details.parent_category=Overordna kategori
profile.user_name=Brukarnamn
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Generer ny API-nykel
profile.generate_new_api_key_info=Endring av passord vil generere ein ny API-nykel
profile.opml_export=OPML-eksport
profile.delete_account=Slett brukar
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=REST API
about.keyboard_shortcuts=Hurtigtastar

View File

@@ -6,6 +6,7 @@ global.download=Pobierz
global.link=Odnośnik
global.bookmark=Zakładka
global.close=Zamknij
global.tags=Tags ####### Needs translation
tree.subscribe=Subskrybuj
tree.import=Importuj
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Sortuj od najnowszego/najstarszego
toolbar.titles_only=Widok listy
toolbar.expanded_view=Widok rozwinięty
toolbar.mark_all_as_read=Oznacz wszystko jako przeczytane
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=Elementy starsze niż dzień
toolbar.mark_all_older_week=Elementy starsze niż tydzień
toolbar.mark_all_older_two_weeks=Elementy starsze niż dwa tygodnie
@@ -66,6 +68,8 @@ settings.general.show_unread=Pokaż kanały i kategorie bez nieprzeczytanych ele
settings.general.social_buttons=Pokaż przyciski udostępniania
settings.general.scroll_marks=W widoku rozwiniętym przewijanie oznacza elementy jako przeczytane
settings.appearance=Wygląd
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=Motyw
settings.submit_your_theme=Wyślij swój motyw
settings.custom_css=Własny styl CSS
@@ -83,7 +87,10 @@ details.queued_for_refresh=W kolejce do odświeżenia
details.feed_url=URL kanału
details.generate_api_key_first=Najpierw wygeneruj klucz API w swoim profilu.
details.unsubscribe=Cofnij subskrypcje
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
details.category_details=Szczegóły kategorii
details.tag_details=Tag details ####### Needs translation
details.parent_category=Kategoria nadrzędna
profile.user_name=Nazwa użytkownika
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Wygeneruj nowy klucz API
profile.generate_new_api_key_info=Zmiana hasła spowoduje wygenerowanie nowego klucza API
profile.opml_export=Eksportuj do pliku OPML
profile.delete_account=Usuń konto
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=REST API
about.keyboard_shortcuts=Skróty klawiszowe

View File

@@ -6,6 +6,7 @@ global.download=Download
global.link=Link
global.bookmark=Favorito
global.close=Fechar
global.tags=Tags ####### Needs translation
tree.subscribe=Inscrever-se
tree.import=Importar
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Ordenar por data cresc/decres
toolbar.titles_only=Somente títulos
toolbar.expanded_view=Modo Expandido
toolbar.mark_all_as_read=Marcar tudo como lido
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=Itens mais antigos que um dia
toolbar.mark_all_older_week=Itens mais antigos que uma semana
toolbar.mark_all_older_two_weeks=Itens mais antigos que duas semanas
@@ -66,6 +68,8 @@ settings.general.show_unread=Mostrar feeds e categorias sem itens não lidos
settings.general.social_buttons=Mostrar botões de mídias sociais
settings.general.scroll_marks=No modo expandido, percorrer os itens marca-os como lidos
settings.appearance=Aparência
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=Tema
settings.submit_your_theme=Envie seu tema
settings.custom_css=CSS personalizado
@@ -83,7 +87,10 @@ details.queued_for_refresh=Na fila para atualizar
details.feed_url=URL do feed
details.generate_api_key_first=Gerar uma chave de API em seu perfil primeiro.
details.unsubscribe=Cancelar inscrição
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
details.category_details=Detalhes da categoria
details.tag_details=Tag details ####### Needs translation
details.parent_category=Categoria pai
profile.user_name=Nome de usuário
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Gerar nova chave de API
profile.generate_new_api_key_info=Mudar a senha irá gerar uma nova chave de API
profile.opml_export=Exportar OPML
profile.delete_account=Excluir conta
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=API REST
about.keyboard_shortcuts=Atalhos de teclado

View File

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

View File

@@ -6,6 +6,7 @@ global.download=Stiahnuť
global.link=Link
global.bookmark=Záložky
global.close=Zavrieť
global.tags=Tagy
tree.subscribe=Odoberať
tree.import=Importovať
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Zoradiť podľa najnovšieho/najstaršieho
toolbar.titles_only=Náhľad titulkov
toolbar.expanded_view=Rozšírený náhľad
toolbar.mark_all_as_read=Označiť všetky ako prečítané
toolbar.mark_all_older_12_hours=Položky staršie ako 12 hodín
toolbar.mark_all_older_day=Položky staršie ako deň
toolbar.mark_all_older_week=Položky staršie ako týždeň
toolbar.mark_all_older_two_weeks=Položky staršie ako dva týždne
@@ -66,6 +68,8 @@ settings.general.show_unread=Zobraziť príspevky a kategórie bez neprečítan
settings.general.social_buttons=Zobraziť možnosti zdieľania
settings.general.scroll_marks=Scrollovanie v rozšírenom náhľade označí položky ako prečítané
settings.appearance=Vzhľad
settings.scroll_speed=Rýchlosť skrolovania—pohybu medzi položkami (v milisekundách)
settings.scroll_speed.help=nastavte 0 pre deaktiváciu
settings.theme=Motív
settings.submit_your_theme=Nahrať vlastný motív vzhľadu
settings.custom_css=Vlastný motív vzhľadu (CSS)
@@ -83,7 +87,10 @@ details.queued_for_refresh=Vo fronte
details.feed_url=URL RSS zdroja
details.generate_api_key_first=Vygenerujte si API kľúč vo vašom profile.
details.unsubscribe=Zrušiť odoberanie.
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
details.category_details=Detaily kategórie
details.tag_details=Detaily tagu
details.parent_category=Hlavná kategória
profile.user_name=Uživateľské meno
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Vygenerovať nový API kľúč
profile.generate_new_api_key_info=Zmenou hesla vygenerujete nový API kľúč
profile.opml_export=exportovať do formátu OPML
profile.delete_account=Odstrániť účet
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=REST API
about.keyboard_shortcuts=Klávesové skratky
@@ -146,3 +154,4 @@ about.shortcuts.font_size=zmeniť veľkosť písma pre vybranú položku
about.shortcuts.go_to_all=zobraziť všetky položky
about.shortcuts.go_to_starred=zobraziť obľúbené položiek
about.shortcuts.feed_search=presun na odoberaný RSS zdroj vložením jeho názvu

View File

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

View File

@@ -6,6 +6,7 @@ global.download=İndir
global.link=Bağlantı
global.bookmark=Yer imi
global.close=Kapat
global.tags=Tags ####### Needs translation
tree.subscribe=Abone ol
tree.import=İçe aktar
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Tarihe göre sırala artan/azalan
toolbar.titles_only=Sadece başlıklar
toolbar.expanded_view=Genişletilmiş görünüm
toolbar.mark_all_as_read=Tümünü okundu işaretle
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=Bir günden eski yazılar
toolbar.mark_all_older_week=Bir haftadan eski yazılar
toolbar.mark_all_older_two_weeks=İki haftadan eski yazılar
@@ -66,6 +68,8 @@ settings.general.show_unread=Okunmamış öğesi bulunan yayın ve kategorileri
settings.general.social_buttons=Sosyal paylaşım butonlarını göster
settings.general.scroll_marks=Genişletilmiş görünümde götüntülenen iletileri okunmuş işaretle
settings.appearance=Görünüm
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=Tema
settings.submit_your_theme=Tema gönder
settings.custom_css=Kişiselleştirilmiş CSS
@@ -83,7 +87,10 @@ details.queued_for_refresh=Yenilenmek üzere kuyrukta
details.feed_url=Yayın URL'si
details.generate_api_key_first=Öncelikle profilinizden bir API anahtarı oluşturun.
details.unsubscribe=Aboneliği iptal et
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
details.category_details=Kategori detayları
details.tag_details=Tag details ####### Needs translation
details.parent_category=Üst kategori
profile.user_name=Kullanıcı adı
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Yeni bir API anahtarı oluştur
profile.generate_new_api_key_info=Şifre değiştirmek API anahtarının da değiştirilmesine neden olcak.
profile.opml_export=OPML dışa aktar
profile.delete_account=Hesabı sil
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=REST API
about.keyboard_shortcuts=Klavye kısayolları

View File

@@ -6,6 +6,7 @@ global.download=下载
global.link=链接
global.bookmark=书签
global.close=关闭
global.tags=Tags ####### Needs translation
tree.subscribe=订阅
tree.import=导入
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=按日期升序/降序排序
toolbar.titles_only=仅显示标题
toolbar.expanded_view=显示内容
toolbar.mark_all_as_read=标记所有为已读
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
toolbar.mark_all_older_day=早于一天的条目
toolbar.mark_all_older_week=早于一周的条目
toolbar.mark_all_older_two_weeks=早于两周的条目
@@ -66,6 +68,8 @@ settings.general.show_unread=显示未读的订阅和目录条目
settings.general.social_buttons=显示分享按钮
settings.general.scroll_marks=在扩展视图中,可滚动条目将其标记为已读
settings.appearance=外观
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
settings.theme=主题
settings.submit_your_theme=提交你的主题
settings.custom_css=自定义 CSS 样式
@@ -83,7 +87,10 @@ details.queued_for_refresh=放入等待刷新的队列
details.feed_url=订阅地址
details.generate_api_key_first=在您的配置文件中首先生成一个 API 密钥。
details.unsubscribe=取消订阅
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
details.category_details=目录详情
details.tag_details=Tag details ####### Needs translation
details.parent_category=上一层目录
profile.user_name=用户名
@@ -98,6 +105,7 @@ profile.generate_new_api_key=生成一个新的 API 密钥
profile.generate_new_api_key_info=修改密码将会生成一个新的的 API 密钥
profile.opml_export=导出 OPML
profile.delete_account=删除帐号
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
about.rest_api=重置 API
about.keyboard_shortcuts=快捷键

View File

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

View File

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

View File

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

View File

@@ -2,26 +2,51 @@
<groups xmlns="http://www.isdc.ro/wro">
<group name="lib">
<js minimize="false">/vendor/jquery/*.js</js>
<js minimize="false">/vendor/lodash/*.js</js>
<js minimize="false">webjar:lodash.min.js</js>
<js minimize="false">webjar:jquery/1.11.0/jquery.min.js</js>
<js>webjar:jquery.mousewheel.js</js>
<js minimize="false">/vendor/jqueryui/*.js</js>
<js minimize="false">/vendor/jquery-mousewheel/*.js</js>
<js minimize="false">/vendor/bootstrap/*.js</js>
<js minimize="false">/vendor/angularjs/*.js</js>
<js minimize="false">/vendor/angularui/*.js</js>
<js minimize="false">/vendor/angularui-bootstrap/*.js</js>
<js minimize="false">/vendor/angularui-state/*.js</js>
<js minimize="false">/vendor/mousetrap/*.js</js>
<js minimize="false">/vendor/nggrid/*.js</js>
<js minimize="false">/vendor/nginfinitescroll/*.js</js>
<js minimize="false">/vendor/spinjs/*.js</js>
<js minimize="false">/vendor/momentjs/*.js</js>
<css minimize="false">/vendor/bootstrap/*.css</css>
<css minimize="false">/vendor/angularui/*.css</css>
<css minimize="false">/vendor/jqueryui/*.css</css>
<js minimize="false">webjar:bootstrap.min.js</js>
<css minimize="false">webjar:bootstrap.min.css</css>
<css minimize="false">/vendor/fontawesome/css/*.css</css>
<css minimize="false">/vendor/zocial/*.css</css>
<css minimize="false">/vendor/nggrid/*.css</css>
<js minimize="false">/vendor/select2/*.js</js>
<css>/vendor/select2/*.css</css>
<js minimize="false">webjar:mousetrap.min.js</js>
<js minimize="false">webjar:device.min.js</js>
<js minimize="false">webjar:moment.min.js</js>
<js minimize="false">webjar:langs.min.js</js>
<js minimize="false">webjar:angular.min.js</js>
<js minimize="false">webjar:angular-resource.min.js</js>
<js minimize="false">webjar:angular-route.min.js</js>
<js minimize="false">webjar:angular-sanitize.min.js</js>
<js minimize="false">webjar:angular-touch.min.js</js>
<js minimize="false">webjar:angular-animate.min.js</js>
<js minimize="false">webjar:angular-ui-router.min.js</js>
<js minimize="false">webjar:ui-utils.min.js</js>
<js>webjar:ui-select2.js</js>
<js minimize="false">webjar:ui-bootstrap-tpls.min.js</js>
<js minimize="false">webjar:ng-infinite-scroll.min.js</js>
<js minimize="false">webjar:ng-grid.min.js</js>
<css minimize="false">webjar:ng-grid.min.css</css>
<js>/vendor/angular-loading-bar/loading-bar.0.4.0.min.js</js>
<css minimize="false">/vendor/angular-loading-bar/loading-bar.min.css</css>
<css>/vendor/zocial/*.css</css>
<css>/vendor/readabilicons/css/*.css</css>
</group>
<group name="app">

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 305 KiB

After

Width:  |  Height:  |  Size: 193 KiB

View File

@@ -30,36 +30,16 @@ module.run(['$rootScope', function($rootScope) {
});
}]);
module.controller('SubscribeCtrl', ['$scope', 'FeedService', 'CategoryService', 'MobileService',
function($scope, FeedService, CategoryService, MobileService) {
module.controller('SubscribeCtrl', ['$scope', '$location', 'FeedService', 'CategoryService', 'MobileService',
function($scope, $location, FeedService, CategoryService, MobileService) {
$scope.opts = {
backdropFade : true,
dialogFade : true
$scope.sub = {
categoryId : 'all'
};
$scope.isOpen = false;
$scope.isOpenImport = false;
$scope.sub = {};
$scope.CategoryService = CategoryService;
$scope.MobileService = MobileService;
$scope.search = function() {
$scope.$emit('emitFeedSearch');
};
$scope.open = function() {
$scope.sub = {
categoryId : $scope.sub.categoryId || 'all'
};
$scope.isOpen = true;
};
$scope.close = function() {
$scope.isOpen = false;
};
// 'ok', 'loading' or 'failed'
$scope.state = 'ok';
$scope.urlChanged = function() {
@@ -72,9 +52,11 @@ module.controller('SubscribeCtrl', ['$scope', 'FeedService', 'CategoryService',
$scope.state = 'ok';
$scope.sub.title = data.title;
$scope.sub.url = data.url;
$scope.stacktrace = null;
}, function(data) {
$scope.state = 'failed';
$scope.sub.title = 'Loading failed. Invalid feed?';
$scope.stacktrace = data.data;
});
}
};
@@ -88,59 +70,97 @@ module.controller('SubscribeCtrl', ['$scope', 'FeedService', 'CategoryService',
}
FeedService.subscribe($scope.sub, function() {
CategoryService.init();
$scope.close();
$location.path('/');
}, function(data) {
$scope.state = 'failed';
$scope.sub.title = 'ERROR: ' + data.data;
});
};
$scope.openImport = function() {
$scope.isOpenImport = true;
$scope.back = function() {
$location.path('/');
};
}]);
$scope.closeImport = function() {
$scope.isOpenImport = false;
};
module.controller('NewCategoryCtrl', ['$scope', '$location', 'FeedService', 'CategoryService', 'MobileService',
function($scope, $location, FeedService, CategoryService, MobileService) {
$scope.cat = {};
$scope.CategoryService = CategoryService;
$scope.MobileService = MobileService;
$scope.openCategory = function() {
$scope.isOpenCategory = true;
$scope.cat = {
parentId : 'all'
};
};
$scope.closeCategory = function() {
$scope.isOpenCategory = false;
$scope.cat = {
parentId : 'all'
};
$scope.saveCategory = function() {
CategoryService.add($scope.cat, function() {
CategoryService.init();
});
$scope.closeCategory();
$location.path('/');
};
$scope.back = function() {
$location.path('/');
};
}]);
module.controller('CategoryTreeCtrl', ['$scope', '$timeout', '$stateParams', '$window', '$location', '$state', '$route', 'CategoryService',
module.controller('ImportCtrl', ['$scope', '$location', 'FeedService', 'CategoryService', 'MobileService',
function($scope, $location, FeedService, CategoryService, MobileService) {
$scope.back = function() {
$location.path('/');
};
}]);
module.controller('CategoryTreeCtrl', [
'$scope',
'$timeout',
'$stateParams',
'$window',
'$location',
'$state',
'$route',
'CategoryService',
'AnalyticsService',
function($scope, $timeout, $stateParams, $window, $location, $state, $route, CategoryService, AnalyticsService) {
'EntryService',
'MobileService',
function($scope, $timeout, $stateParams, $window, $location, $state, $route, CategoryService, AnalyticsService, EntryService,
MobileService) {
$scope.selectedType = $stateParams._type;
$scope.selectedId = $stateParams._id;
$scope.EntryService = EntryService;
$scope.MobileService = MobileService;
$scope.starred = {
id : 'starred',
name : 'Starred'
};
$scope.tags = [];
$scope.$watch('EntryService.tags', function(newValue, oldValue) {
if (newValue) {
$scope.tags = [];
_.each(newValue, function(e) {
$scope.tags.push({
id : e,
name : e,
isTag : true
});
});
}
}, true);
$scope.$on('$stateChangeSuccess', function() {
$scope.selectedType = $stateParams._type;
$scope.selectedId = $stateParams._id;
});
$scope.resizeCallback = function(event, ui) {
$('.main-content').css('margin-left', $(ui.element).outerWidth(true) + 'px');
};
$timeout(function refreshTree() {
AnalyticsService.track();
CategoryService.refresh(function() {
@@ -176,7 +196,7 @@ module.controller('CategoryTreeCtrl', ['$scope', '$timeout', '$stateParams', '$w
$scope.$watch(rootUnreadCount, function(value) {
var label = 'CommaFeed';
if (value > 0) {
label = value + ' - ' + label;
label = '(' + value + ') ' + label;
}
$window.document.title = label;
});
@@ -201,7 +221,7 @@ module.controller('CategoryTreeCtrl', ['$scope', '$timeout', '$stateParams', '$w
var getCurrentIndex = function(id, type, flat) {
var index = -1;
for ( var i = 0; i < flat.length; i++) {
for (var i = 0; i < flat.length; i++) {
var node = flat[i];
if (node[0] == id && node[1] == type) {
index = i;
@@ -266,8 +286,8 @@ module.controller('CategoryTreeCtrl', ['$scope', '$timeout', '$stateParams', '$w
});
}]);
module.controller('FeedDetailsCtrl', ['$scope', '$state', '$stateParams', 'FeedService', 'CategoryService', 'ProfileService', '$dialog',
function($scope, $state, $stateParams, FeedService, CategoryService, ProfileService, $dialog) {
module.controller('FeedDetailsCtrl', ['$scope', '$state', '$stateParams', 'FeedService', 'CategoryService', 'ProfileService',
function($scope, $state, $stateParams, FeedService, CategoryService, ProfileService) {
$scope.CategoryService = CategoryService;
$scope.user = ProfileService.get();
@@ -288,30 +308,15 @@ module.controller('FeedDetailsCtrl', ['$scope', '$state', '$stateParams', 'FeedS
$scope.unsubscribe = function() {
var sub = $scope.sub;
var title = 'Unsubscribe';
var msg = 'Unsubscribe from ' + sub.name + '?';
var btns = [{
result : 'cancel',
label : 'Cancel'
}, {
result : 'ok',
label : 'OK',
cssClass : 'btn-primary'
}];
$dialog.messageBox(title, msg, btns).open().then(function(result) {
if (result == 'ok') {
var data = {
id : sub.id
};
FeedService.unsubscribe(data, function() {
CategoryService.init();
});
$state.transitionTo('feeds.view', {
_id : 'all',
_type : 'category'
});
}
var data = {
id : sub.id
};
FeedService.unsubscribe(data, function() {
CategoryService.init();
});
$state.transitionTo('feeds.view', {
_id : 'all',
_type : 'category'
});
};
@@ -333,7 +338,7 @@ module.controller('FeedDetailsCtrl', ['$scope', '$state', '$stateParams', 'FeedS
}]);
module.controller('CategoryDetailsCtrl', ['$scope', '$state', '$stateParams', 'FeedService', 'CategoryService', 'ProfileService',
'$dialog', function($scope, $state, $stateParams, FeedService, CategoryService, ProfileService, $dialog) {
function($scope, $state, $stateParams, FeedService, CategoryService, ProfileService) {
$scope.CategoryService = CategoryService;
$scope.user = ProfileService.get();
@@ -355,7 +360,7 @@ module.controller('CategoryDetailsCtrl', ['$scope', '$state', '$stateParams', 'F
};
return;
}
for ( var i = 0; i < CategoryService.flatCategories.length; i++) {
for (var i = 0; i < CategoryService.flatCategories.length; i++) {
var cat = CategoryService.flatCategories[i];
if (cat.id == $stateParams._id) {
$scope.category = {
@@ -380,29 +385,14 @@ module.controller('CategoryDetailsCtrl', ['$scope', '$state', '$stateParams', 'F
$scope.deleteCategory = function() {
var category = $scope.category;
var title = 'Delete category';
var msg = 'Delete category ' + category.name + ' ?';
var btns = [{
result : 'cancel',
label : 'Cancel'
}, {
result : 'ok',
label : 'OK',
cssClass : 'btn-primary'
}];
$dialog.messageBox(title, msg, btns).open().then(function(result) {
if (result == 'ok') {
CategoryService.remove({
id : category.id
}, function() {
CategoryService.init();
});
$state.transitionTo('feeds.view', {
_id : 'all',
_type : 'category'
});
}
CategoryService.remove({
id : category.id
}, function() {
CategoryService.init();
});
$state.transitionTo('feeds.view', {
_id : 'all',
_type : 'category'
});
};
@@ -423,9 +413,23 @@ module.controller('CategoryDetailsCtrl', ['$scope', '$state', '$stateParams', 'F
};
}]);
module.controller('TagDetailsCtrl', ['$scope', '$state', '$stateParams', 'FeedService', 'CategoryService', 'ProfileService',
function($scope, $state, $stateParams, FeedService, CategoryService, ProfileService) {
$scope.CategoryService = CategoryService;
$scope.user = ProfileService.get();
$scope.tag = $stateParams._id;
$scope.back = function() {
$state.transitionTo('feeds.view', {
_id : $scope.tag,
_type : 'tag'
});
};
}]);
module.controller('ToolbarCtrl', [
'$scope',
'$http',
'$state',
'$stateParams',
'$route',
@@ -437,24 +441,15 @@ module.controller('ToolbarCtrl', [
'ServerService',
'FeedService',
'MobileService',
function($scope, $http, $state, $stateParams, $route, $location, SettingsService, EntryService, ProfileService, AnalyticsService,
function($scope, $state, $stateParams, $route, $location, SettingsService, EntryService, ProfileService, AnalyticsService,
ServerService, FeedService, MobileService) {
function totalActiveAjaxRequests() {
return ($http.pendingRequests.length + $.active);
}
$scope.keywords = $location.search().q;
$scope.session = ProfileService.get();
$scope.ServerService = ServerService.get();
$scope.settingsService = SettingsService;
$scope.MobileService = MobileService;
$scope.loading = true;
$scope.$watch(totalActiveAjaxRequests, function() {
$scope.loading = (totalActiveAjaxRequests() !== 0);
});
$scope.$watch('settingsService.settings.readingMode', function(newValue, oldValue) {
if (newValue && oldValue && newValue != oldValue) {
SettingsService.save();
@@ -502,6 +497,10 @@ module.controller('ToolbarCtrl', [
markAll();
};
$scope.markAll12Hours = function() {
markAll(new Date().getTime() - 43200000);
};
$scope.markAllDay = function() {
markAll(new Date().getTime() - 86400000);
};
@@ -515,9 +514,10 @@ module.controller('ToolbarCtrl', [
};
$scope.search = function() {
$location.search('q', $scope.keywords);
var keywords = this.keywords;
$location.search('q', keywords);
$scope.$emit('emitEntrySearch', {
keywords : $scope.keywords
keywords : keywords
});
};
$scope.showButtons = function() {
@@ -570,7 +570,7 @@ module.controller('FeedSearchCtrl', ['$scope', '$state', '$filter', '$timeout',
}
var filtered = $scope.filtered;
for ( var i = 0; i < filtered.length; i++) {
for (var i = 0; i < filtered.length; i++) {
if ($scope.focus.id == filtered[i].id) {
index = i;
break;
@@ -672,8 +672,9 @@ module.controller('FeedListCtrl', [
'FeedService',
'CategoryService',
'AnalyticsService',
'MobileService',
function($scope, $stateParams, $http, $route, $state, $window, $timeout, $location, EntryService, SettingsService, FeedService,
CategoryService, AnalyticsService) {
CategoryService, AnalyticsService, MobileService) {
$window = angular.element($window);
AnalyticsService.track();
@@ -688,9 +689,11 @@ module.controller('FeedListCtrl', [
$scope.errorCount = 0;
$scope.timestamp = 0;
$scope.entries = [];
$scope.ignored_read_status = false;
$scope.font_size = 0;
$scope.settingsService = SettingsService;
$scope.MobileService = MobileService;
$scope.$watch('settingsService.settings.readingMode', function(newValue, oldValue) {
if (newValue && oldValue && newValue != oldValue) {
$scope.$emit('emitReload');
@@ -702,6 +705,12 @@ module.controller('FeedListCtrl', [
}
});
$scope.$watch('settingsService.settings.readingOrder', function(newValue, oldValue) {
if (newValue && oldValue && newValue != oldValue) {
$scope.$emit('emitReload');
}
});
$scope.limit = SettingsService.settings.viewMode == 'title' ? 10 : 5;
$scope.busy = false;
$scope.hasMore = true;
@@ -714,7 +723,9 @@ module.controller('FeedListCtrl', [
$scope.busy = true;
var limit = $scope.limit;
var offset = SettingsService.settings.readingMode == 'all' ? $scope.entries.length : _.where($scope.entries, {
var read_shown = SettingsService.settings.readingMode === 'all' || $scope.ignored_read_status;
var offset = read_shown ? $scope.entries.length : _.where($scope.entries, {
read : false
}).length;
if ($scope.entries.length === 0) {
@@ -729,7 +740,7 @@ module.controller('FeedListCtrl', [
}
var callback = function(data) {
for ( var i = 0; i < data.entries.length; i++) {
for (var i = 0; i < data.entries.length; i++) {
var entry = data.entries[i];
if (!_.some($scope.entries, {
id : entry.id
@@ -744,17 +755,26 @@ module.controller('FeedListCtrl', [
$scope.busy = false;
$scope.hasMore = data.hasMore;
$scope.feedLink = data.feedLink;
$scope.ignored_read_status = data.ignoredReadStatus;
};
var service = $scope.selectedType == 'feed' ? FeedService : CategoryService;
service.entries({
var data = {
id : $scope.selectedId,
readType : $scope.keywords ? 'all' : $scope.settingsService.settings.readingMode,
order : $scope.settingsService.settings.readingOrder,
offset : offset,
limit : limit,
keywords : $scope.keywords
}, callback);
};
if ($scope.selectedType == 'feed') {
FeedService.entries(data, callback);
} else if ($scope.selectedType == 'category') {
CategoryService.entries(data, callback);
} else if ($scope.selectedType == 'tag') {
data.tag = data.id;
data.id = 'all';
CategoryService.entries(data, callback);
}
};
var watch_scrolling = true;
@@ -781,10 +801,11 @@ module.controller('FeedListCtrl', [
return;
} else {
var scrollTop = elemTop - $('#toolbar').outerHeight();
var speed = SettingsService.settings.scrollSpeed;
watch_scrolling = false;
$('html, body').animate({
scrollTop : scrollTop
}, 400, 'swing', function() {
}, speed, 'swing', function() {
watch_scrolling = true;
});
}
@@ -803,7 +824,7 @@ module.controller('FeedListCtrl', [
var docTop = w.scrollTop();
var current = null;
for ( var i = 0; i < $scope.entries.length; i++) {
for (var i = 0; i < $scope.entries.length; i++) {
var entry = $scope.entries[i];
var e = $('#entry_' + entry.id);
if (e.offset().top + e.height() > docTop + $('#toolbar').outerHeight()) {
@@ -866,7 +887,7 @@ module.controller('FeedListCtrl', [
$scope.markUpTo = function(entry) {
var entries = [];
for ( var i = 0; i < $scope.entries.length; i++) {
for (var i = 0; i < $scope.entries.length; i++) {
var e = $scope.entries[i];
if (!e.read) {
entries.push({
@@ -905,7 +926,7 @@ module.controller('FeedListCtrl', [
var getCurrentIndex = function() {
var index = -1;
if ($scope.current) {
for ( var i = 0; i < $scope.entries.length; i++) {
for (var i = 0; i < $scope.entries.length; i++) {
if ($scope.current == $scope.entries[i]) {
index = i;
break;
@@ -1194,6 +1215,7 @@ module.controller('FeedListCtrl', [
Mousetrap.bind('f', function(e) {
$('body').toggleClass('full-screen');
$('.main-content').css('margin-left', '');
return false;
});
@@ -1257,6 +1279,30 @@ module.controller('ManageUsersCtrl', ['$scope', '$state', '$location', 'AdminUse
multiSelect : false,
showColumnMenu : true,
showFilter : true,
columnDefs : [{
field : 'id',
displayName : 'ID'
}, {
field : 'name',
displayName : 'Name'
}, {
field : 'email',
cellClass : 'E-Mail'
}, {
field : 'created',
cellClass : 'Created',
cellFilter : 'entryDate'
}, {
field : 'lastLogin',
cellClass : 'Last login',
cellFilter : 'entryDate'
}, {
field : 'admin',
cellClass : 'Admin'
}, {
field : 'enabled',
cellClass : 'Enabled'
}],
afterSelectionChange : function(item) {
$state.transitionTo('admin.useredit', {
@@ -1273,8 +1319,8 @@ module.controller('ManageUsersCtrl', ['$scope', '$state', '$location', 'AdminUse
};
}]);
module.controller('ManageUserCtrl', ['$scope', '$state', '$stateParams', '$dialog', 'AdminUsersService',
function($scope, $state, $stateParams, $dialog, AdminUsersService) {
module.controller('ManageUserCtrl', ['$scope', '$state', '$stateParams', 'AdminUsersService',
function($scope, $state, $stateParams, AdminUsersService) {
$scope.user = $stateParams._id ? AdminUsersService.get({
id : $stateParams._id
}) : {
@@ -1301,76 +1347,14 @@ module.controller('ManageUserCtrl', ['$scope', '$state', '$stateParams', '$dialo
}, alertFunction);
};
$scope.remove = function() {
var title = 'Delete user';
var msg = 'Delete user ' + $scope.user.name + ' ?';
var btns = [{
result : 'cancel',
label : 'Cancel'
}, {
result : 'ok',
label : 'OK',
cssClass : 'btn-primary'
}];
$dialog.messageBox(title, msg, btns).open().then(function(result) {
if (result == 'ok') {
AdminUsersService.remove({
id : $scope.user.id
}, function() {
$state.transitionTo('admin.userlist');
}, alertFunction);
}
});
AdminUsersService.remove({
id : $scope.user.id
}, function() {
$state.transitionTo('admin.userlist');
}, alertFunction);
};
}]);
module.controller('ManageDuplicateFeedsCtrl', ['$scope', 'AdminCleanupService', function($scope, AdminCleanupService) {
$scope.limit = 10;
$scope.page = 0;
$scope.minCount = 1;
$scope.mode = 'NORMALIZED_URL';
$scope.mergeData = {};
$scope.refreshData = function() {
AdminCleanupService.findDuplicateFeeds({
mode : $scope.mode,
limit : $scope.limit,
page : $scope.page,
minCount : $scope.minCount
}, function(data) {
$scope.counts = data;
});
};
$scope.autoMerge = function() {
var callback = function() {
alert('done!');
};
for ( var i = 0; i < $scope.counts.length; i++) {
var count = $scope.counts[i];
if (count.autoMerge) {
AdminCleanupService.mergeFeeds({
intoFeedId : count.feeds[0].id,
feedIds : _.pluck(count.feeds, 'id')
}, callback);
}
}
};
$scope.focus = function(count) {
$scope.current = count;
$scope.mergeData.intoFeedId = count.feeds[0].id;
$scope.mergeData.feedIds = _.pluck(count.feeds, 'id');
};
$scope.merge = function() {
AdminCleanupService.mergeFeeds($scope.mergeData, function() {
alert('done!');
});
};
}]);
module.controller('SettingsCtrl', ['$scope', '$location', 'SettingsService', 'AnalyticsService', 'ServerService',
function($scope, $location, SettingsService, AnalyticsService, ServerService) {
@@ -1378,7 +1362,7 @@ module.controller('SettingsCtrl', ['$scope', '$location', 'SettingsService', 'An
$scope.ServerService = ServerService.get();
$scope.themes = ['default', 'ebraminio', 'MRACHINI', 'svetla'];
$scope.themes = ['default', 'bootstrap', 'dark', 'ebraminio', 'MRACHINI', 'svetla', 'third'];
$scope.settingsService = SettingsService;
$scope.$watch('settingsService.settings', function(value) {
@@ -1398,8 +1382,8 @@ module.controller('SettingsCtrl', ['$scope', '$location', 'SettingsService', 'An
};
}]);
module.controller('ProfileCtrl', ['$scope', '$location', '$dialog', 'ProfileService', 'AnalyticsService',
function($scope, $location, $dialog, ProfileService, AnalyticsService) {
module.controller('ProfileCtrl', ['$scope', '$location', 'ProfileService', 'AnalyticsService',
function($scope, $location, ProfileService, AnalyticsService) {
AnalyticsService.track();
@@ -1423,23 +1407,8 @@ module.controller('ProfileCtrl', ['$scope', '$location', '$dialog', 'ProfileServ
});
};
$scope.deleteAccount = function() {
var title = 'Delete account';
var msg = 'Delete your acount? There\'s no turning back!';
var btns = [{
result : 'cancel',
label : 'Cancel'
}, {
result : 'ok',
label : 'OK',
cssClass : 'btn-primary'
}];
$dialog.messageBox(title, msg, btns).open().then(function(result) {
if (result == 'ok') {
ProfileService.deleteAccount({});
window.location.href = 'logout';
}
});
ProfileService.deleteAccount({});
window.location.href = 'logout';
};
}]);
@@ -1460,9 +1429,6 @@ module.controller('ManageSettingsCtrl', ['$scope', '$location', '$state', 'Admin
$scope.toUsers = function() {
$state.transitionTo('admin.userlist');
};
$scope.toCleanup = function() {
$state.transitionTo('admin.duplicate_feeds');
};
}]);
module.controller('HelpController', ['$scope', 'CategoryService', 'AnalyticsService', 'ServerService',
@@ -1477,11 +1443,13 @@ module.controller('HelpController', ['$scope', 'CategoryService', 'AnalyticsServ
}]);
module.controller('FooterController', ['$scope', function($scope) {
module.controller('FooterController', ['$scope', '$sce', function($scope, $sce) {
var baseUrl = window.location.href.substring(0, window.location.href.lastIndexOf('#'));
var hostname = window.location.hostname;
$scope.subToMeUrl = baseUrl + 'rest/feed/subscribe?url={feed}';
$scope.subToMeName = hostname.indexOf('www.commafeed.com') !== -1 ? 'CommaFeed' : 'CommaFeed (' + hostname + ')';
var url = baseUrl + 'rest/feed/subscribe?url={feed}';
var name = hostname.indexOf('www.commafeed.com') !== -1 ? 'CommaFeed' : 'CommaFeed (' + hostname + ')';
var subToMeUrl = 'https://www.subtome.com/register-no-ui.html?name=' + name + '&url=' + url;
$scope.subToMeUrl = $sce.trustAsResourceUrl(subToMeUrl);
}]);

View File

@@ -15,6 +15,22 @@ module.directive('focus', ['$timeout', function($timeout) {
};
}]);
module.directive('confirmClick', [function() {
return {
priority : -1,
restrict : 'A',
link : function(scope, element, attrs) {
element.bind('click', function(e) {
var message = attrs.confirmClick;
if (message && !confirm(message)) {
e.stopImmediatePropagation();
e.preventDefault();
}
});
}
};
}]);
/**
* Open a popup window pointing to the url in the href attribute
*/
@@ -30,6 +46,41 @@ module.directive('popup', function() {
};
});
/**
* entry tag handling
*/
module.directive('tags', function() {
return {
restrict : 'E',
scope : {
entry : '='
},
replace : true,
templateUrl : 'templates/_tags.html',
controller : ['$scope', 'EntryService', function($scope, EntryService) {
$scope.select2Options = {
'multiple' : true,
'simple_tags' : true,
'maximumInputLength' : 40,
tags : EntryService.tags
};
$scope.$watch('entry.tags', function(newValue, oldValue) {
if (oldValue && newValue != oldValue) {
var data = {
entryId : $scope.entry.id,
tags : []
};
if (newValue) {
data.tags = newValue.split(',');
}
EntryService.tag(data);
}
}, true);
}]
};
});
/**
* Reusable favicon component
*/
@@ -116,13 +167,14 @@ module.directive('category', [function() {
selectedId : '=',
showLabel : '=',
showChildren : '=',
unreadCount : '&'
unreadCount : '&',
tag : '='
},
restrict : 'E',
replace : true,
templateUrl : 'templates/_category.html',
controller : ['$scope', '$state', '$dialog', 'FeedService', 'CategoryService', 'SettingsService', 'MobileService',
function($scope, $state, $dialog, FeedService, CategoryService, SettingsService, MobileService) {
controller : ['$scope', '$state', 'FeedService', 'CategoryService', 'SettingsService', 'MobileService',
function($scope, $state, FeedService, CategoryService, SettingsService, MobileService) {
$scope.settingsService = SettingsService;
$scope.getClass = function(level) {
@@ -174,13 +226,14 @@ module.directive('category', [function() {
}
};
$scope.categoryClicked = function(id) {
$scope.categoryClicked = function(id, isTag) {
MobileService.toggleLeftMenu();
if ($scope.selectedType == 'category' && id == $scope.selectedId) {
var type = isTag ? 'tag' : 'category';
if ($scope.selectedType == type && id == $scope.selectedId) {
$scope.$emit('emitReload');
} else {
$state.transitionTo('feeds.view', {
_type : 'category',
_type : type,
_id : id
});
}
@@ -192,10 +245,16 @@ module.directive('category', [function() {
});
};
$scope.showCategoryDetails = function(category) {
$state.transitionTo('feeds.category_details', {
_id : category.id
});
$scope.showCategoryDetails = function(id, isTag) {
if (isTag) {
$state.transitionTo('feeds.tag_details', {
_id : id
});
} else {
$state.transitionTo('feeds.category_details', {
_id : id
});
}
};
$scope.toggleCategory = function(category, event) {
@@ -214,46 +273,6 @@ module.directive('category', [function() {
};
}]);
/**
* Reusable spinner component
*/
module.directive('spinner', function() {
return {
scope : {
shown : '='
},
restrict : 'A',
link : function($scope, element) {
element.addClass('spinner');
var opts = {
lines : 11, // The number of lines to draw
length : 5, // The length of each line
width : 3, // The line thickness
radius : 8, // The radius of the inner circle
corners : 1, // Corner roundness (0..1)
rotate : 0, // The rotation offset
color : '#000', // #rgb or #rrggbb
speed : 1.3, // Rounds per second
trail : 60, // Afterglow percentage
shadow : false, // Whether to render a shadow
hwaccel : true, // Whether to use hardware acceleration
zIndex : 2e9, // The z-index (defaults to 2000000000)
top : 'auto', // Top position relative to parent in px
left : 'auto' // Left position relative to parent in px
};
var spinner = new Spinner(opts);
$scope.$watch('shown', function(shown) {
if (shown) {
spinner.spin();
element.append(spinner.el);
} else {
spinner.stop();
}
});
}
};
});
module.directive('draggable', function() {
return {
restrict : 'A',

View File

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

View File

@@ -1,10 +1,13 @@
var app = angular.module('commafeed', ['ui', 'ui.bootstrap', 'ui.state', 'commafeed.directives', 'commafeed.controllers',
'commafeed.services', 'commafeed.filters', 'ngSanitize', 'infinite-scroll', 'ngGrid']);
var app = angular.module('commafeed', ['ngRoute', 'ngTouch', 'ngAnimate', 'ui.utils', 'ui.bootstrap', 'ui.router', 'ui.select2',
'commafeed.directives', 'commafeed.controllers', 'commafeed.services', 'commafeed.filters', 'ngSanitize', 'infinite-scroll',
'ngGrid', 'chieffancypants.loadingBar']);
app.config(['$routeProvider', '$stateProvider', '$urlRouterProvider', '$httpProvider', '$compileProvider',
function($routeProvider, $stateProvider, $urlRouterProvider, $httpProvider, $compileProvider) {
app.config(['$routeProvider', '$stateProvider', '$urlRouterProvider', '$httpProvider', '$compileProvider', 'cfpLoadingBarProvider',
function($routeProvider, $stateProvider, $urlRouterProvider, $httpProvider, $compileProvider, cfpLoadingBarProvider) {
cfpLoadingBarProvider.includeSpinner = false;
$compileProvider.urlSanitizationWhitelist(/^\s*(https?|ftp|mailto|javascript):/);
$compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|javascript):/);
var interceptor = ['$rootScope', '$q', function(scope, $q) {
var success = function(response) {
@@ -39,6 +42,21 @@ app.config(['$routeProvider', '$stateProvider', '$urlRouterProvider', '$httpProv
templateUrl : 'templates/feeds.view.html',
controller : 'FeedListCtrl'
});
$stateProvider.state('feeds.subscribe', {
url : '/subscribe',
templateUrl : 'templates/feeds.subscribe.html',
controller : 'SubscribeCtrl'
});
$stateProvider.state('feeds.new_category', {
url : '/add_category',
templateUrl : 'templates/feeds.new_category.html',
controller : 'NewCategoryCtrl'
});
$stateProvider.state('feeds.import', {
url : '/import',
templateUrl : 'templates/feeds.import.html',
controller : 'ImportCtrl'
});
$stateProvider.state('feeds.search', {
url : '/search/:_keywords',
templateUrl : 'templates/feeds.view.html',
@@ -54,6 +72,11 @@ app.config(['$routeProvider', '$stateProvider', '$urlRouterProvider', '$httpProv
templateUrl : 'templates/feeds.category_details.html',
controller : 'CategoryDetailsCtrl'
});
$stateProvider.state('feeds.tag_details', {
url : '/details/tag/:_id',
templateUrl : 'templates/feeds.tag_details.html',
controller : 'TagDetailsCtrl'
});
$stateProvider.state('feeds.help', {
url : '/help',
templateUrl : 'templates/feeds.help.html',
@@ -90,11 +113,6 @@ app.config(['$routeProvider', '$stateProvider', '$urlRouterProvider', '$httpProv
templateUrl : 'templates/admin.useredit.html',
controller : 'ManageUserCtrl'
});
$stateProvider.state('admin.duplicate_feeds', {
url : '/feeds/duplicates',
templateUrl : 'templates/admin.duplicate_feeds.html',
controller : 'ManageDuplicateFeedsCtrl'
});
$stateProvider.state('admin.settings', {
url : '/settings',
templateUrl : 'templates/admin.settings.html',
@@ -105,7 +123,7 @@ app.config(['$routeProvider', '$stateProvider', '$urlRouterProvider', '$httpProv
templateUrl : 'templates/admin.metrics.html',
controller : 'MetricsCtrl'
});
$urlRouterProvider.when('/', '/feeds/view/category/all');
$urlRouterProvider.when('/admin', '/admin/settings');
$urlRouterProvider.otherwise('/');

View File

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

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