]> git.wh0rd.org Git - tt-rss.git/blob - tt-rss.js
add viewfeed_offline(); misc fixes
[tt-rss.git] / tt-rss.js
1 var total_unread = 0;
2 var first_run = true;
3 var display_tags = false;
4 var global_unread = -1;
5 var active_title_text = "";
6 var current_subtitle = "";
7 var daemon_enabled = false;
8 var daemon_refresh_only = false;
9 //var _qfd_deleted_feed = 0;
10 var firsttime_update = true;
11 var _active_feed_id = 0;
12 var _active_feed_is_cat = false;
13 var number_of_feeds = 0;
14 var sanity_check_done = false;
15 var _hfd_scrolltop = 0;
16 var hotkey_prefix = false;
17 var init_params = new Object();
18 var ver_offset = 0;
19 var hor_offset = 0;
20 var feeds_sort_by_unread = false;
21 var feedlist_sortable_enabled = false;
22 var offline_mode = false;
23 var store = false;
24 var localServer = false;
25 var db = false;
26 var download_progress_last = 0;
27 var offline_dl_max_id = 0;
28
29 function activeFeedIsCat() {
30         return _active_feed_is_cat;
31 }
32
33 function getActiveFeedId() {
34 //      return getCookie("ttrss_vf_actfeed");
35         try {
36                 debug("gAFID: " + _active_feed_id);
37                 return _active_feed_id;
38         } catch (e) {
39                 exception_error("getActiveFeedId", e);
40         }
41 }
42
43 function setActiveFeedId(id, is_cat) {
44 //      return setCookie("ttrss_vf_actfeed", id);
45         try {
46                 debug("sAFID(" + id + ", " + is_cat + ")");
47                 _active_feed_id = id;
48
49                 if (is_cat != undefined) {
50                         _active_feed_is_cat = is_cat;
51                 }
52
53         } catch (e) {
54                 exception_error("setActiveFeedId", e);
55         }
56 }
57
58
59 function isFeedlistSortable() {
60         return feedlist_sortable_enabled;
61 }
62
63 function tagsAreDisplayed() {
64         return display_tags;
65 }
66
67 function toggleTags(show_all) {
68
69         try {
70
71         debug("toggleTags: " + show_all + "; " + display_tags);
72
73         var p = document.getElementById("dispSwitchPrompt");
74
75         if (!show_all && !display_tags) {
76                 displayDlg("printTagCloud");
77         } else if (show_all) {
78                 closeInfoBox();
79                 display_tags = true;
80                 p.innerHTML = __("display feeds");
81                 notify_progress("Loading, please wait...", true);
82                 updateFeedList();
83         } else if (display_tags) {
84                 display_tags = false;
85                 p.innerHTML = __("tag cloud");
86                 notify_progress("Loading, please wait...", true);
87                 updateFeedList();
88         }
89
90         } catch (e) {
91                 exception_error("toggleTags", e);
92         }
93 }
94
95 function dlg_frefresh_callback(transport, deleted_feed) {
96         if (getActiveFeedId() == deleted_feed) {
97                 var h = document.getElementById("headlines-frame");
98                 if (h) {
99                         h.innerHTML = "<div class='whiteBox'>" + __('No feed selected.') + "</div>";
100                 }
101         }
102
103         setTimeout('updateFeedList(false, false)', 50);
104         closeInfoBox();
105 }
106
107 function refetch_callback2(transport) {
108         try {
109
110                 var date = new Date();
111
112                 parse_counters_reply(transport, true);
113
114                 debug("refetch_callback2: done");
115
116 /*              if (!daemon_enabled && !daemon_refresh_only) {
117                         notify_info("All feeds updated.");
118                         updateTitle("");
119                 } else {
120                         //notify("");
121                 } */
122         } catch (e) {
123                 exception_error("refetch_callback", e);
124                 updateTitle("");
125         }
126 }
127
128 function backend_sanity_check_callback(transport) {
129
130         try {
131
132                 if (sanity_check_done) {
133                         fatalError(11, "Sanity check request received twice. This can indicate "+
134                       "presence of Firebug or some other disrupting extension. "+
135                                 "Please disable it and try again.");
136                         return;
137                 }
138
139                 if (!transport.responseXML) {
140                         if (!window.google && !google.gears) {
141                                 fatalError(3, "Sanity check: Received reply is not XML", transport.responseText);
142                         } else {
143                                 init_offline();
144                         }
145                         return;
146                 }
147
148                 if (getURLParam("offline")) {
149                         return init_offline();
150                 }
151
152                 var reply = transport.responseXML.firstChild.firstChild;
153
154                 if (!reply) {
155                         fatalError(3, "Sanity check: invalid RPC reply", transport.responseText);
156                         return;
157                 }
158
159                 var error_code = reply.getAttribute("error-code");
160         
161                 if (error_code && error_code != 0) {
162                         return fatalError(error_code, reply.getAttribute("error-msg"));
163                 }
164
165                 debug("sanity check ok");
166
167                 var params = reply.nextSibling;
168
169                 if (params) {
170                         debug('reading init-params...');
171                         var param = params.firstChild;
172
173                         while (param) {
174                                 var k = param.getAttribute("key");
175                                 var v = param.getAttribute("value");
176                                 debug(k + " => " + v);
177                                 init_params[k] = v;                                     
178                                 param = param.nextSibling;
179                         }
180                 }
181
182                 sanity_check_done = true;
183
184                 init_second_stage();
185
186         } catch (e) {
187                 exception_error("backend_sanity_check_callback", e, transport); 
188         } 
189 }
190
191 function scheduleFeedUpdate(force) {
192
193         debug("in scheduleFeedUpdate");
194
195 /*      if (!daemon_enabled && !daemon_refresh_only) {
196                 notify_progress("Updating feeds...", true);
197         } */
198
199         var query_str = "backend.php?op=rpc&subop=";
200
201         if (force) {
202                 query_str = query_str + "forceUpdateAllFeeds";
203         } else {
204                 query_str = query_str + "updateAllFeeds";
205         }
206
207         var omode;
208
209         if (firsttime_update && !navigator.userAgent.match("Opera")) {
210                 firsttime_update = false;
211                 omode = "T";
212         } else {
213                 if (display_tags) {
214                         omode = "tl";
215                 } else {
216                         omode = "flc";
217                 }
218         }
219         
220         query_str = query_str + "&omode=" + omode;
221         query_str = query_str + "&uctr=" + global_unread;
222
223         var date = new Date();
224         var timestamp = Math.round(date.getTime() / 1000);
225         query_str = query_str + "&ts=" + timestamp
226
227         debug("REFETCH query: " + query_str);
228
229         new Ajax.Request(query_str, {
230                 onComplete: function(transport) { 
231                                 refetch_callback2(transport); 
232                         } });
233 }
234
235 function updateFeedList(silent, fetch) {
236
237 //      if (silent != true) {
238 //              notify("Loading feed list...");
239 //      }
240
241         debug("<b>updateFeedList</b>");
242
243         var query_str = "backend.php?op=feeds";
244
245         if (display_tags) {
246                 query_str = query_str + "&tags=1";
247         }
248
249         if (getActiveFeedId() && !activeFeedIsCat()) {
250                 query_str = query_str + "&actid=" + getActiveFeedId();
251         }
252
253         var date = new Date();
254         var timestamp = Math.round(date.getTime() / 1000);
255         query_str = query_str + "&ts=" + timestamp
256         
257         if (fetch) query_str = query_str + "&fetch=yes";
258
259 //      var feeds_frame = document.getElementById("feeds-frame");
260 //      feeds_frame.src = query_str;
261
262         debug("updateFeedList Q=" + query_str);
263
264         new Ajax.Request(query_str, {
265                 onComplete: function(transport) { 
266                         feedlist_callback2(transport); 
267                 } });
268
269 }
270
271 function catchupAllFeeds() {
272
273         var str = __("Mark all articles as read?");
274
275         if (getInitParam("confirm_feed_catchup") != 1 || confirm(str)) {
276
277                 var query_str = "backend.php?op=feeds&subop=catchupAll";
278
279                 notify_progress("Marking all feeds as read...");
280
281                 debug("catchupAllFeeds Q=" + query_str);
282
283                 new Ajax.Request(query_str, {
284                         onComplete: function(transport) { 
285                                 feedlist_callback2(transport); 
286                         } });
287
288                 global_unread = 0;
289                 updateTitle("");
290         }
291 }
292
293 function viewCurrentFeed(subop) {
294
295 //      if (getActiveFeedId()) {
296         if (getActiveFeedId() != undefined) {
297                 viewfeed(getActiveFeedId(), subop, activeFeedIsCat());
298         } else {
299                 disableContainerChildren("headlinesToolbar", false, document);
300 //              viewfeed(-1, subop); // FIXME
301         }
302         return false; // block unneeded form submits
303 }
304
305 function viewfeed(feed, subop) {
306         var f = window.frames["feeds-frame"];
307         f.viewfeed(feed, subop);
308 }
309
310 function timeout() {
311         if (getInitParam("bw_limit") == "1") return;
312
313         scheduleFeedUpdate(false);
314
315         var refresh_time = getInitParam("feeds_frame_refresh");
316
317         if (!refresh_time) refresh_time = 600; 
318
319         setTimeout("timeout()", refresh_time*1000);
320 }
321
322 function resetSearch() {
323         var searchbox = document.getElementById("searchbox")
324
325         if (searchbox.value != "" && getActiveFeedId()) {       
326                 searchbox.value = "";
327                 viewfeed(getActiveFeedId(), "");
328         }
329 }
330
331 function searchCancel() {
332         closeInfoBox(true);
333 }
334
335 function search() {
336         closeInfoBox(); 
337         viewCurrentFeed(0, "");
338 }
339
340 // if argument is undefined, current subtitle is not updated
341 // use blank string to clear subtitle
342 function updateTitle(s) {
343         var tmp = "Tiny Tiny RSS";
344
345         if (s != undefined) {
346                 current_subtitle = s;
347         }
348
349         if (global_unread > 0) {
350                 tmp = tmp + " (" + global_unread + ")";
351         }
352
353         if (current_subtitle) {
354                 tmp = tmp + " - " + current_subtitle;
355         }
356
357         if (active_title_text.length > 0) {
358                 tmp = tmp + " > " + active_title_text;
359         }
360
361         document.title = tmp;
362 }
363
364 function genericSanityCheck() {
365
366 //      if (!Ajax.getTransport()) fatalError(1);
367
368         setCookie("ttrss_vf_test", "TEST");
369         
370         if (getCookie("ttrss_vf_test") != "TEST") {
371                 fatalError(2);
372         }
373
374         return true;
375 }
376
377 function init() {
378
379         try {
380
381                 // this whole shebang is based on http://www.birnamdesigns.com/misc/busted2.html
382
383                 if (arguments.callee.done) return;
384                 arguments.callee.done = true;           
385
386                 init_gears();
387
388                 disableContainerChildren("headlinesToolbar", true);
389
390                 Form.disable("main_toolbar_form");
391
392                 if (!genericSanityCheck()) 
393                         return;
394
395                 if (getURLParam('debug')) {
396                         Element.show("debug_output");
397                         debug('debug mode activated');
398                 }
399
400                 var params = "&ua=" + param_escape(navigator.userAgent);
401
402                 loading_set_progress(30);
403
404                 new Ajax.Request("backend.php?op=rpc&subop=sanityCheck" + params,       {
405                         onComplete: function(transport) {
406                                         backend_sanity_check_callback(transport);
407                                 } });
408
409         } catch (e) {
410                 exception_error("init", e);
411         }
412 }
413
414 function resize_headlines(delta_x, delta_y) {
415
416         try {
417
418                 debug("resize_headlines: " + delta_x + ":" + delta_y);
419         
420                 var h_frame = document.getElementById("headlines-frame");
421                 var c_frame = document.getElementById("content-frame");
422                 var f_frame = document.getElementById("footer");
423                 var feeds_frame = document.getElementById("feeds-holder");
424                 var resize_grab = document.getElementById("resize-grabber");
425                 var resize_handle = document.getElementById("resize-handle");
426
427                 if (!c_frame || !h_frame) return;
428         
429                 if (feeds_frame && getInitParam("theme") == "compat") {
430                                 feeds_frame.style.bottom = f_frame.offsetHeight + "px";         
431                 }
432         
433                 if (getInitParam("theme") == "3pane") {
434         
435                         if (delta_x != undefined) {
436                                 if (c_frame.offsetLeft - delta_x > feeds_frame.offsetWidth + feeds_frame.offsetLeft + 100 && c_frame.offsetWidth + delta_x > 100) {
437                                         hor_offset = hor_offset + delta_x;
438                                 }
439                         }
440         
441                         debug("resize_headlines: HOR-mode: " + hor_offset);
442         
443                         c_frame.style.width = (400 + hor_offset) + "px";
444                         h_frame.style.right = c_frame.offsetWidth - 1 + "px";
445         
446                         resize_grab.style.top = (h_frame.offsetTop + h_frame.offsetHeight - 60) + "px";
447                         resize_grab.style.left = (h_frame.offsetLeft + h_frame.offsetWidth - 
448                                 4) + "px";
449                         resize_grab.style.display = "block";
450
451                         resize_handle.src = "themes/3pane/images/resize_handle_vert.png";
452                         resize_handle.style.paddingTop = (resize_grab.offsetHeight / 2 - 7) + "px";
453         
454                 } else {
455         
456                         if (delta_y != undefined) {
457                                 if (c_frame.offsetHeight + delta_y > 100 && h_frame.offsetHeight - delta_y > 100) {
458                                         ver_offset = ver_offset + delta_y;
459                                 }
460                         }
461         
462                         debug("resize_headlines: VER-mode: " + ver_offset);
463         
464                         h_frame.style.height = (300 - ver_offset) + "px";
465         
466                         c_frame.style.top = (h_frame.offsetTop + h_frame.offsetHeight + 0) + "px";
467                         h_frame.style.height = h_frame.offsetHeight + "px";
468         
469                         var theme_c = 0;
470         
471                         if (getInitParam("theme") == "graycube") {
472                                 theme_c = 1;
473                         }
474
475                         if (getInitParam("theme") == "graycube" || getInitParam("theme") == "compat") {
476                                 resize_handle.src = "themes/graycube/images/resize_handle_horiz.png";
477                         }
478         
479 /*                      resize_grab.style.top = (h_frame.offsetTop + h_frame.offsetHeight - 
480                                 4 - theme_c) + "px";
481                         resize_grab.style.display = "block"; */
482         
483                 }
484         
485                 if (getInitParam("cookie_lifetime") != 0) {
486                         setCookie("ttrss_offset_ver", ver_offset, 
487                                 getInitParam("cookie_lifetime"));
488                         setCookie("ttrss_offset_hor", hor_offset, 
489                                 getInitParam("cookie_lifetime"));
490                 } else {
491                         setCookie("ttrss_offset_ver", ver_offset);
492                         setCookie("ttrss_offset_hor", hor_offset);
493                 }
494
495         } catch (e) {
496                 exception_error("resize_headlines", e);
497         }
498
499 }
500
501 function init_second_stage() {
502
503         try {
504
505                 delCookie("ttrss_vf_test");
506
507 //              document.onresize = resize_headlines;
508
509                 var toolbar = document.forms["main_toolbar_form"];
510
511                 dropboxSelect(toolbar.view_mode, getInitParam("default_view_mode"));
512                 dropboxSelect(toolbar.limit, getInitParam("default_view_limit"));
513                 dropboxSelect(toolbar.order_by, getInitParam("default_view_order_by"));
514
515                 daemon_enabled = getInitParam("daemon_enabled") == 1;
516                 daemon_refresh_only = getInitParam("daemon_refresh_only") == 1;
517                 feeds_sort_by_unread = getInitParam("feeds_sort_by_unread") == 1;
518
519                 var fl = cache_find_param("FEEDLIST", getInitParam("num_feeds"));
520
521                 if (fl) {
522                         render_feedlist(fl);
523                         if (document.getElementById("feedList")) {
524                                 request_counters();
525                         } else {
526                                 setTimeout('updateFeedList(false, false)', 50);
527                         }
528                 } else {
529                         setTimeout('updateFeedList(false, false)', 50);
530                 }
531
532                 debug("second stage ok");
533
534                 loading_set_progress(60);
535
536                 ver_offset = parseInt(getCookie("ttrss_offset_ver"));
537                 hor_offset = parseInt(getCookie("ttrss_offset_hor"));
538
539                 debug("got offsets from cookies: ver " + ver_offset + " hor " + hor_offset);
540
541                 /* fuck IE */
542
543                 if (isNaN(hor_offset)) hor_offset = 0;
544                 if (isNaN(ver_offset)) ver_offset = 0;
545
546                 debug("offsets from cookies [x:y]: " + hor_offset + ":" + ver_offset);
547
548                 resize_headlines();
549
550         } catch (e) {
551                 exception_error("init_second_stage", e);
552         }
553 }
554
555 function quickMenuChange() {
556         var chooser = document.getElementById("quickMenuChooser");
557         var opid = chooser[chooser.selectedIndex].value;
558
559         chooser.selectedIndex = 0;
560         quickMenuGo(opid);
561 }
562
563 function quickMenuGo(opid) {
564         try {
565
566                 if (opid == "qmcPrefs") {
567                         gotoPreferences();
568                 }
569         
570                 if (opid == "qmcSearch") {
571                         displayDlg("search", getActiveFeedId() + ":" + activeFeedIsCat());
572                         return;
573                 }
574         
575                 if (opid == "qmcAddFeed") {
576                         displayDlg("quickAddFeed");
577                         return;
578                 }
579
580                 if (opid == "qmcEditFeed") {
581                         editFeedDlg(getActiveFeedId());
582                 }
583         
584                 if (opid == "qmcRemoveFeed") {
585                         var actid = getActiveFeedId();
586
587                         if (activeFeedIsCat()) {
588                                 alert(__("You can't unsubscribe from the category."));
589                                 return;
590                         }       
591
592                         if (!actid) {
593                                 alert(__("Please select some feed first."));
594                                 return;
595                         }
596
597                         var fn = getFeedName(actid);
598
599                         var pr = __("Unsubscribe from %s?").replace("%s", fn);
600
601                         if (confirm(pr)) {
602                                 unsubscribeFeed(actid);
603                         }
604                 
605                         return;
606                 }
607
608                 if (opid == "qmcClearFeed") {
609                         var actid = getActiveFeedId();
610
611                         if (!actid) {
612                                 alert(__("Please select some feed first."));
613                                 return;
614                         }
615
616                         if (activeFeedIsCat() || actid < 0) {
617                                 alert(__("You can't clear this type of feed."));
618                                 return;
619                         }       
620
621                         var fn = getFeedName(actid);
622
623                         var pr = __("Erase all non-starred articles in %s?").replace("%s", fn);
624
625                         if (confirm(pr)) {
626                                 clearFeedArticles(actid);
627                         }
628                 
629                         return;
630                 }
631         
632
633                 if (opid == "qmcUpdateFeeds") {
634                         scheduleFeedUpdate(true);
635                         return;
636                 }
637         
638                 if (opid == "qmcCatchupAll") {
639                         catchupAllFeeds();
640                         return;
641                 }
642         
643                 if (opid == "qmcShowOnlyUnread") {
644                         toggleDispRead();
645                         return;
646                 }
647         
648                 if (opid == "qmcAddFilter") {
649                         displayDlg("quickAddFilter", getActiveFeedId());
650                 }
651
652                 if (opid == "qmcAddLabel") {
653                         addLabel();
654                 }
655
656                 if (opid == "qmcRescoreFeed") {
657                         rescoreCurrentFeed();
658                 }
659
660                 if (opid == "qmcHKhelp") {
661                         //Element.show("hotkey_help_overlay");
662                         Effect.Appear("hotkey_help_overlay", {duration : 0.3});
663                 }
664
665                 if (opid == "qmcResetUI") {
666                         hor_offset = 0;
667                         ver_offset = 0;
668                         resize_headlines();
669                 }
670
671                 if (opid == "qmcDownload") {
672                         displayDlg("offlineDownload");
673                         return;
674                 }
675
676                 if (opid == "qmcResetCats") {
677
678                         if (confirm(__("Reset category order?"))) {
679
680                                 var query = "backend.php?op=feeds&subop=catsortreset";
681
682                                 notify_progress("Loading, please wait...", true);
683
684                                 new Ajax.Request(query, {
685                                         onComplete: function(transport) { 
686                                                 window.setTimeout('updateFeedList(false, false)', 50);
687                                         } });
688                         }
689                 }
690
691         } catch (e) {
692                 exception_error("quickMenuGo", e);
693         }
694 }
695
696 function unsubscribeFeed(feed_id, title) {
697
698
699         var msg = __("Unsubscribe from %s?").replace("%s", title);
700
701         if (title == undefined || confirm(msg)) {
702                 notify_progress("Removing feed...");
703
704                 var query = "backend.php?op=pref-feeds&quiet=1&subop=remove&ids=" + feed_id;
705
706                 new Ajax.Request(query, {
707                         onComplete: function(transport) {
708                                         dlg_frefresh_callback(transport, feed_id);
709                                 } });
710         }
711
712         return false;
713 }
714
715
716 function updateFeedTitle(t) {
717         active_title_text = t;
718         updateTitle();
719 }
720
721 function toggleDispRead() {
722         try {
723
724                 var hide_read_feeds = (getInitParam("hide_read_feeds") == "1");
725
726                 hide_read_feeds = !hide_read_feeds;
727
728                 debug("toggle_disp_read => " + hide_read_feeds);
729
730                 hideOrShowFeeds(hide_read_feeds);
731
732                 storeInitParam("hide_read_feeds", hide_read_feeds, true);
733                                 
734         } catch (e) {
735                 exception_error("toggleDispRead", e);
736         }
737 }
738
739 function parse_runtime_info(elem) {
740         if (!elem) {
741                 debug("parse_runtime_info: elem is null, aborting");
742                 return;
743         }
744
745         var param = elem.firstChild;
746
747         debug("parse_runtime_info: " + param);
748
749         while (param) {
750                 var k = param.getAttribute("key");
751                 var v = param.getAttribute("value");
752
753                 debug("RI: " + k + " => " + v);
754
755                 if (k == "num_feeds") {
756                         init_params[k] = v;                                     
757                 }
758
759                 if (k == "new_version_available") {
760                         var icon = document.getElementById("newVersionIcon");
761                         if (icon) {
762                                 if (v == "1") {
763                                         icon.style.display = "inline";
764                                 } else {
765                                         icon.style.display = "none";
766                                 }
767                         }
768                 }
769
770                 var error_flag;
771
772                 if (k == "daemon_is_running" && v != 1) {
773                         notify_error("<span onclick=\"javascript:explainError(1)\">Update daemon is not running.</span>", true);
774                         error_flag = true;
775                 }
776
777                 if (k == "daemon_stamp_ok" && v != 1) {
778                         notify_error("<span onclick=\"javascript:explainError(3)\">Update daemon is not updating feeds.</span>", true);
779                         error_flag = true;
780                 }
781
782                 if (!error_flag) {
783                         notify('');
784                 }
785
786 /*              var w = document.getElementById("noDaemonWarning");
787                 
788                 if (w) {
789                         if (k == "daemon_is_running" && v != 1) {
790                                 w.style.display = "block";
791                         } else {
792                                 w.style.display = "none";
793                         }
794                 } */
795                 param = param.nextSibling;
796         }
797 }
798
799 function catchupCurrentFeed() {
800
801         var fn = getFeedName(getActiveFeedId(), activeFeedIsCat());
802         
803         var str = __("Mark all articles in %s as read?").replace("%s", fn);
804
805         if (getInitParam("confirm_feed_catchup") != 1 || confirm(str)) {
806                 return viewCurrentFeed('MarkAllRead')
807         }
808 }
809
810 function catchupFeedInGroup(id) {
811
812         try {
813
814                 var title = getFeedName(id);
815
816                 var str = __("Mark all articles in %s as read?").replace("%s", title);
817
818                 if (getInitParam("confirm_feed_catchup") != 1 || confirm(str)) {
819                         return viewCurrentFeed('MarkAllReadGR:' + id)
820                 }
821
822         } catch (e) {
823                 exception_error("catchupFeedInGroup", e);
824         }
825 }
826
827 function editFeedDlg(feed) {
828         try {
829
830                 if (!feed) {
831                         alert(__("Please select some feed first."));
832                         return;
833                 }
834         
835                 if ((feed <= 0) || activeFeedIsCat() || tagsAreDisplayed()) {
836                         alert(__("You can't edit this kind of feed."));
837                         return;
838                 }
839         
840                 var query = "";
841         
842                 if (feed > 0) {
843                         query = "backend.php?op=pref-feeds&subop=editfeed&id=" +        param_escape(feed);
844                 } else {
845                         query = "backend.php?op=pref-labels&subop=edit&id=" +   param_escape(-feed-11);
846                 }
847
848                 disableHotkeys();
849
850                 new Ajax.Request(query, {
851                         onComplete: function(transport) { 
852                                 infobox_callback2(transport); 
853                         } });
854
855         } catch (e) {
856                 exception_error("editFeedDlg", e);
857         }
858 }
859
860 /* this functions duplicate those of prefs.js feed editor, with
861         some differences because there is no feedlist */
862
863 function feedEditCancel() {
864         closeInfoBox();
865         return false;
866 }
867
868 function feedEditSave() {
869
870         try {
871         
872                 // FIXME: add parameter validation
873
874                 var query = Form.serialize("edit_feed_form");
875
876                 notify_progress("Saving feed...");
877
878                 new Ajax.Request("backend.php", {
879                         parameters: query,
880                         onComplete: function(transport) { 
881                                 dlg_frefresh_callback(transport); 
882                         } });
883
884
885                 closeInfoBox();
886
887                 return false;
888
889         } catch (e) {
890                 exception_error("feedEditSave (main)", e);
891         } 
892 }
893
894 function clearFeedArticles(feed_id) {
895
896         notify_progress("Clearing feed...");
897
898         var query = "backend.php?op=pref-feeds&quiet=1&subop=clear&id=" + feed_id;
899
900         new Ajax.Request(query, {
901                 onComplete: function(transport) {
902                                 dlg_frefresh_callback(transport, feed_id);
903                         } });
904
905         return false;
906 }
907
908 function collapse_feedlist() {
909         try {
910                 debug("toggle_feedlist");
911                 
912                 var theme = getInitParam("theme");
913                 if (theme != "" && theme != "compact" && theme != "graycube" &&
914                                 theme != "compat") return;
915
916                 var fl = document.getElementById("feeds-holder");
917                 var fh = document.getElementById("headlines-frame");
918                 var fc = document.getElementById("content-frame");
919                 var ft = document.getElementById("toolbar");
920                 var ff = document.getElementById("footer");
921                 var fhdr = document.getElementById("header");
922                 var fbtn = document.getElementById("collapse_feeds_btn");
923
924                 if (!Element.visible(fl)) {
925                         Element.show(fl);
926                         fbtn.value = "<<";
927
928                         if (theme != "graycube") {
929
930                                 fh.style.left = fl.offsetWidth + "px";
931                                 ft.style.left = fl.offsetWidth + "px";
932                                 if (fc) fc.style.left = fl.offsetWidth + "px";
933                                 if (ff && theme != "compat") ff.style.left = (fl.offsetWidth-1) + "px";
934
935                                 if (theme == "compact") fhdr.style.left = (fl.offsetWidth + 10) + "px";
936                         } else {
937                                 fh.style.left = fl.offsetWidth + 40 + "px";
938                                 ft.style.left = fl.offsetWidth + 40 +"px";
939                                 if (fc) fc.style.left = fl.offsetWidth + 40 + "px";
940                         }
941
942                         setCookie("ttrss_vf_fclps", "0");
943
944                 } else {
945                         Element.hide(fl);
946                         fbtn.value = ">>";
947
948                         if (theme != "graycube") {
949
950                                 fh.style.left = "0px";
951                                 ft.style.left = "0px";
952                                 if (fc) fc.style.left = "0px";
953                                 if (ff) ff.style.left = "0px";
954
955                                 if (theme == "compact") fhdr.style.left = "10px";
956
957                         } else {
958                                 fh.style.left = "20px";
959                                 ft.style.left = "20px";
960                                 if (fc) fc.style.left = "20px";
961
962                         }
963
964                         setCookie("ttrss_vf_fclps", "1");
965                 }
966         } catch (e) {
967                 exception_error("toggle_feedlist", e);
968         }
969 }
970
971 function viewModeChanged() {
972         cache_empty();
973         return viewCurrentFeed(0, '')
974 }
975
976 function viewLimitChanged() {
977         cache_empty();
978         return viewCurrentFeed(0, '')
979 }
980
981 /* function adjustArticleScore(id, score) {
982         try {
983
984                 var pr = prompt(__("Assign score to article:"), score);
985
986                 if (pr != undefined) {
987                         var query = "backend.php?op=rpc&subop=setScore&id=" + id + "&score=" + pr;
988
989                         new Ajax.Request(query, {
990                         onComplete: function(transport) {
991                                         viewCurrentFeed();
992                                 } });
993
994                 }
995         } catch (e) {
996                 exception_error("adjustArticleScore", e);
997         }
998 } */
999
1000 function rescoreCurrentFeed() {
1001
1002         var actid = getActiveFeedId();
1003
1004         if (activeFeedIsCat() || actid < 0 || tagsAreDisplayed()) {
1005                 alert(__("You can't rescore this kind of feed."));
1006                 return;
1007         }       
1008
1009         if (!actid) {
1010                 alert(__("Please select some feed first."));
1011                 return;
1012         }
1013
1014         var fn = getFeedName(actid);
1015         var pr = __("Rescore articles in %s?").replace("%s", fn);
1016
1017         if (confirm(pr)) {
1018                 notify_progress("Rescoring articles...");
1019
1020                 var query = "backend.php?op=pref-feeds&subop=rescore&quiet=1&ids=" + actid;
1021
1022                 new Ajax.Request(query, {
1023                 onComplete: function(transport) {
1024                         viewCurrentFeed();
1025                 } });
1026         }
1027 }
1028
1029 function hotkey_handler(e) {
1030
1031         try {
1032
1033                 var keycode;
1034                 var shift_key = false;
1035
1036                 var feedlist = document.getElementById('feedList');
1037
1038                 try {
1039                         shift_key = e.shiftKey;
1040                 } catch (e) {
1041
1042                 }
1043         
1044                 if (window.event) {
1045                         keycode = window.event.keyCode;
1046                 } else if (e) {
1047                         keycode = e.which;
1048                 }
1049
1050                 var keychar = String.fromCharCode(keycode);
1051
1052                 if (keycode == 27) { // escape
1053                         if (Element.visible("hotkey_help_overlay")) {
1054                                 Element.hide("hotkey_help_overlay");
1055                         }
1056                         hotkey_prefix = false;
1057                         closeInfoBox();
1058                 } 
1059
1060                 if (!hotkeys_enabled) {
1061                         debug("hotkeys disabled");
1062                         return;
1063                 }
1064
1065                 if (keycode == 16) return; // ignore lone shift
1066
1067                 if ((keycode == 70 || keycode == 67 || keycode == 71) 
1068                                 && !hotkey_prefix) {
1069
1070                         hotkey_prefix = keycode;
1071                         debug("KP: PREFIX=" + keycode + " CHAR=" + keychar);
1072                         return true;
1073                 }
1074
1075                 if (Element.visible("hotkey_help_overlay")) {
1076                         Element.hide("hotkey_help_overlay");
1077                 }
1078
1079                 /* Global hotkeys */
1080
1081                 if (!hotkey_prefix) {
1082
1083                         if (keycode == 68 && shift_key) { // d
1084                                 if (!Element.visible("debug_output")) {
1085                                         Element.show("debug_output");
1086                                         debug('debug mode activated');
1087                                 } else {
1088                                         Element.hide("debug_output");
1089                                 }
1090         
1091                                 return;
1092                         }
1093         
1094                         if ((keycode == 191 || keychar == '?') && shift_key) { // ?
1095                                 if (!Element.visible("hotkey_help_overlay")) {
1096                                         //Element.show("hotkey_help_overlay");
1097                                         Effect.Appear("hotkey_help_overlay", {duration : 0.3});
1098                                 } else {
1099                                         Element.hide("hotkey_help_overlay");
1100                                 }
1101                                 return false;
1102                         }
1103
1104                         if (keycode == 191 || keychar == '/') { // /
1105                                 displayDlg("search", getActiveFeedId() + ":" + activeFeedIsCat());
1106                                 return false;
1107                         }
1108
1109                         if (keycode == 82 && shift_key) { // R
1110                                 scheduleFeedUpdate(true);
1111                                 return;
1112                         }
1113
1114                         if (keycode == 74) { // j
1115                                 var feed = getActiveFeedId();
1116                                 var new_feed = getRelativeFeedId2(feed, activeFeedIsCat(), 'prev');
1117 //                              alert(feed + " IC: " + activeFeedIsCat() + " => " + new_feed);
1118                                 if (new_feed) {
1119                                         var is_cat = new_feed.match("CAT:");
1120                                         if (is_cat) {
1121                                                 new_feed = new_feed.replace("CAT:", "");
1122                                                 viewCategory(new_feed);
1123                                         } else {
1124                                                 viewfeed(new_feed, '', false);
1125                                         }
1126                                 }
1127                                 return;
1128                         }
1129         
1130                         if (keycode == 75) { // k
1131                                 var feed = getActiveFeedId();
1132                                 var new_feed = getRelativeFeedId2(feed, activeFeedIsCat(), 'next');
1133 //                              alert(feed + " IC: " + activeFeedIsCat() + " => " + new_feed);
1134                                 if (new_feed) {
1135                                         var is_cat = new_feed.match("CAT:");
1136                                         if (is_cat == "CAT:") {
1137                                                 new_feed = new_feed.replace("CAT:", "");
1138                                                 viewCategory(new_feed);
1139                                         } else {
1140                                                 viewfeed(new_feed, '', false);
1141                                         }
1142                                 }
1143                                 return;
1144                         }
1145
1146                         if (shift_key && keycode == 40) { // shift-down
1147                                 catchupRelativeToArticle(1);
1148                                 return;
1149                         }
1150
1151                         if (shift_key && keycode == 38) { // shift-up
1152                                 catchupRelativeToArticle(0);
1153                                 return;
1154                         }
1155
1156                         if (shift_key && keycode == 78) { // N
1157                                 scrollArticle(50);      
1158                                 return;
1159                         }
1160
1161                         if (shift_key && keycode == 80) { // P
1162                                 scrollArticle(-50);     
1163                                 return;
1164                         }
1165
1166
1167                         if (keycode == 78 || keycode == 40) { // n, down
1168                                 if (typeof moveToPost != 'undefined') {
1169                                         moveToPost('next');
1170                                         return;
1171                                 }
1172                         }
1173         
1174                         if (keycode == 80 || keycode == 38) { // p, up
1175                                 if (typeof moveToPost != 'undefined') {
1176                                         moveToPost('prev');
1177                                         return;
1178                                 }
1179                         }
1180
1181                         if (keycode == 83 && shift_key) { // S
1182                                 var id = getActiveArticleId();
1183                                 if (id) {                               
1184                                         togglePub(id);
1185                                 }
1186                                 return;
1187                         }
1188
1189                         if (keycode == 83) { // s
1190                                 var id = getActiveArticleId();
1191                                 if (id) {                               
1192                                         toggleMark(id);
1193                                 }
1194                                 return;
1195                         }
1196
1197
1198                         if (keycode == 85) { // u
1199                                 var id = getActiveArticleId();
1200                                 if (id) {                               
1201                                         toggleUnread(id);
1202                                 }
1203                                 return;
1204                         }
1205
1206                         if (keycode == 84 && shift_key) { // T
1207                                 var id = getActiveArticleId();
1208                                 if (id) {
1209                                         editArticleTags(id, getActiveFeedId(), isCdmMode());
1210                                         return;
1211                                 }
1212                         }
1213
1214                         if (keycode == 9) { // tab
1215                                 var id = getArticleUnderPointer();
1216                                 if (id) {                               
1217                                         var cb = document.getElementById("RCHK-" + id);
1218
1219                                         if (cb) {
1220                                                 cb.checked = !cb.checked;
1221                                                 toggleSelectRowById(cb, "RROW-" + id);
1222                                                 return false;
1223                                         }
1224                                 }
1225                         }
1226
1227                         if (keycode == 79) { // o
1228                                 if (getActiveArticleId()) {
1229                                         openArticleInNewWindow(getActiveArticleId());
1230                                         return;
1231                                 }
1232                         }
1233
1234                         if (keycode == 81 && shift_key) { // Q
1235                                 if (typeof catchupAllFeeds != 'undefined') {
1236                                         catchupAllFeeds();
1237                                         return;
1238                                 }
1239                         }
1240
1241                         if (keycode == 88) { // x
1242                                 if (activeFeedIsCat()) {
1243                                         toggleCollapseCat(getActiveFeedId());
1244                                 }
1245                         }
1246                 }
1247
1248                 /* Prefix f */
1249
1250                 if (hotkey_prefix == 70) { // f 
1251
1252                         hotkey_prefix = false;
1253
1254                         if (keycode == 81) { // q
1255                                 if (getActiveFeedId()) {
1256                                         catchupCurrentFeed();
1257                                         return;
1258                                 }
1259                         }
1260
1261                         if (keycode == 82) { // r
1262                                 if (getActiveFeedId()) {
1263                                         viewfeed(getActiveFeedId(), "ForceUpdate", activeFeedIsCat());
1264                                         return;
1265                                 }
1266                         }
1267
1268                         if (keycode == 65) { // a
1269                                 toggleDispRead();
1270                                 return false;
1271                         }
1272
1273                         if (keycode == 85 && shift_key) { // U
1274                                 scheduleFeedUpdate(true);
1275                                 return false;
1276                         }
1277
1278                         if (keycode == 85) { // u
1279                                 if (getActiveFeedId()) {
1280                                         viewfeed(getActiveFeedId(), "ForceUpdate");
1281                                         return false;
1282                                 }
1283                         }
1284
1285                         if (keycode == 69) { // e
1286                                 editFeedDlg(getActiveFeedId());
1287                                 return false;
1288                         }
1289
1290                         if (keycode == 83) { // s
1291                                 displayDlg("quickAddFeed");
1292                                 return false;
1293                         }
1294
1295                         if (keycode == 67 && shift_key) { // C
1296                                 if (typeof catchupAllFeeds != 'undefined') {
1297                                         catchupAllFeeds();
1298                                         return false;
1299                                 }
1300                         }
1301
1302                         if (keycode == 67) { // c
1303                                 if (getActiveFeedId()) {
1304                                         catchupCurrentFeed();
1305                                         return false;
1306                                 }
1307                         }
1308
1309                         if (keycode == 68 && shift_key) { // D
1310                                 initiate_offline_download();
1311                                 return false;
1312                         }
1313
1314                         if (keycode == 68) { // d
1315                                 displayDlg("offlineDownload");
1316                                 return false;
1317                         }
1318
1319                         if (keycode == 87) { // w
1320                                 feeds_sort_by_unread = !feeds_sort_by_unread;
1321                                 return resort_feedlist();
1322                         }
1323
1324                         if (keycode == 72) { // h
1325                                 hideReadHeadlines();
1326                                 return;
1327                         }
1328
1329                 }
1330
1331                 /* Prefix c */
1332
1333                 if (hotkey_prefix == 67) { // c
1334                         hotkey_prefix = false;
1335
1336                         if (keycode == 70) { // f
1337                                 displayDlg("quickAddFilter", getActiveFeedId());
1338                                 return false;
1339                         }
1340
1341                         if (keycode == 76) { // l
1342                                 addLabel();
1343                                 return false;
1344                         }
1345
1346                         if (keycode == 83) { // s
1347                                 if (typeof collapse_feedlist != 'undefined') {
1348                                         collapse_feedlist();
1349                                         return false;
1350                                 }
1351                         }
1352
1353                         if (keycode == 77) { // m
1354                                 feedlist_sortable_enabled = !feedlist_sortable_enabled;
1355                                 if (feedlist_sortable_enabled) {
1356                                         notify_info("Category reordering enabled");
1357                                         toggle_sortable_feedlist(true);
1358                                 } else {
1359                                         notify_info("Category reordering disabled");
1360                                         toggle_sortable_feedlist(false);
1361                                 }
1362                         }
1363
1364                         if (keycode == 78) { // n
1365                                 catchupRelativeToArticle(1);
1366                                 return;
1367                         }
1368
1369                         if (keycode == 80) { // p
1370                                 catchupRelativeToArticle(0);
1371                                 return;
1372                         }
1373
1374
1375                 }
1376
1377                 /* Prefix g */
1378
1379                 if (hotkey_prefix == 71) { // g
1380
1381                         hotkey_prefix = false;
1382
1383
1384                         if (keycode == 65) { // a
1385                                 viewfeed(-4);
1386                                 return false;
1387                         }
1388
1389                         if (keycode == 83) { // s
1390                                 viewfeed(-1);
1391                                 return false;
1392                         }
1393
1394                         if (keycode == 80 && shift_key) { // P
1395                                 gotoPreferences();
1396                                 return false;
1397                         }
1398
1399                         if (keycode == 80) { // p
1400                                 viewfeed(-2);
1401                                 return false;
1402                         }
1403
1404                         if (keycode == 70) { // f
1405                                 viewfeed(-3);
1406                                 return false;
1407                         }
1408
1409                         if (keycode == 84 && shift_key) { // T
1410                                 toggleTags();
1411                                 return false;
1412                         }
1413                 }
1414
1415                 /* Cmd */
1416
1417                 if (hotkey_prefix == 224 || hotkey_prefix == 91) { // f 
1418                         hotkey_prefix = false;
1419                         return;
1420                 }
1421
1422                 if (hotkey_prefix) {
1423                         debug("KP: PREFIX=" + hotkey_prefix + " CODE=" + keycode + " CHAR=" + keychar);
1424                 } else {
1425                         debug("KP: CODE=" + keycode + " CHAR=" + keychar);
1426                 }
1427
1428
1429         } catch (e) {
1430                 exception_error("hotkey_handler", e);
1431         }
1432 }
1433
1434 function feedsSortByUnread() {
1435         return feeds_sort_by_unread;
1436 }
1437
1438 function addLabel() {
1439
1440         try {
1441
1442                 var caption = prompt(__("Please enter label caption:"), "");
1443
1444                 if (caption != undefined) {
1445         
1446                         if (caption == "") {
1447                                 alert(__("Can't create label: missing caption."));
1448                                 return false;
1449                         }
1450
1451                         var query = "backend.php?op=pref-labels&subop=add&caption=" + 
1452                                 param_escape(caption);
1453
1454                         notify_progress("Loading, please wait...", true);
1455
1456                         new Ajax.Request(query, {
1457                                 onComplete: function(transport) { 
1458                                         updateFeedList();
1459                         } });
1460
1461                 }
1462
1463         } catch (e) {
1464                 exception_error("addLabel", e);
1465         }
1466 }
1467
1468 function visitOfficialSite() {
1469         window.open("http://tt-rss.org/");
1470 }
1471
1472
1473 function feedBrowserSubscribe() {
1474         try {
1475
1476                 var selected = getSelectedFeedsFromBrowser();
1477
1478                 if (selected.length > 0) {
1479                         closeInfoBox();
1480
1481                         notify_progress("Loading, please wait...", true);
1482
1483                         var query =  "backend.php?op=pref-feeds&subop=massSubscribe&ids="+
1484                                 param_escape(selected.toString());
1485
1486                         new Ajax.Request(query, {
1487                                 onComplete: function(transport) { 
1488                                         updateFeedList();
1489                                 } });
1490
1491                 } else {
1492                         alert(__("No feeds are selected."));
1493                 }
1494
1495         } catch (e) {
1496                 exception_error("feedBrowserSubscribe", e);
1497         }
1498 }
1499
1500 function init_gears() {
1501         try {
1502
1503                 if (window.google && google.gears) {
1504                         localServer = google.gears.factory.create("beta.localserver");
1505                         store = localServer.createManagedStore("tt-rss");
1506                         db = google.gears.factory.create('beta.database');
1507                         db.open('tt-rss');
1508
1509                         db.execute("CREATE TABLE IF NOT EXISTS cache (id text, article text, param text, added text)");
1510
1511                         db.execute("CREATE TABLE if not exists feeds (id integer, title text, has_icon integer)");
1512
1513                         db.execute("CREATE TABLE if not exists articles (id integer, feed_id integer, title text, link text, guid text, updated text, content text, tags text, unread text, marked text)");
1514
1515                         var qmcDownload = document.getElementById("qmcDownload");
1516                         if (qmcDownload) Element.show(qmcDownload);
1517
1518                 }       
1519         
1520                 cache_expire();
1521
1522         } catch (e) {
1523                 exception_error("init_gears", e);
1524         }
1525 }
1526
1527 function init_offline() {
1528         try {
1529                 offline_mode = true;
1530
1531                 render_offline_feedlist();
1532
1533                 remove_splash();
1534         } catch (e) {
1535                 exception_error("init_offline", e);
1536         }
1537 }
1538
1539 function offline_download_parse(stage, transport) {
1540         try {
1541                 if (transport.responseXML) {
1542
1543                         if (stage == 0) {
1544
1545                                 var feeds = transport.responseXML.getElementsByTagName("feed");
1546
1547                                 if (feeds.length > 0) {
1548                                         db.execute("DELETE FROM feeds");
1549                                 }
1550
1551                                 for (var i = 0; i < feeds.length; i++) {
1552                                         var id = feeds[i].getAttribute("id");
1553                                         var has_icon = feeds[i].getAttribute("has_icon");
1554                                         var title = feeds[i].firstChild.nodeValue;
1555         
1556                                         db.execute("INSERT INTO feeds (id,title,has_icon)"+
1557                                                 "VALUES (?,?,?)",
1558                                                 [id, title, has_icon]);
1559                                 }
1560                 
1561                                 window.setTimeout("initiate_offline_download("+(stage+1)+")", 50);
1562                         } else {
1563
1564                                 var articles = transport.responseXML.getElementsByTagName("article");
1565
1566                                 var articles_found = 0;
1567
1568                                 for (var i = 0; i < articles.length; i++) {                                     
1569                                         var a = eval("("+articles[i].firstChild.nodeValue+")");
1570                                         articles_found++;
1571                                         if (a) {
1572                                                 db.execute("DELETE FROM articles WHERE id = ?", [a.id]);
1573                                                 db.execute("INSERT INTO articles "+
1574                                                 "(id, feed_id, title, link, guid, updated, content, "+
1575                                                         "unread, marked, tags) "+
1576                                                 "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", 
1577                                                         [a.id, a.feed_id, a.title, a.link, a.guid, a.updated, 
1578                                                                 a.content, a.unread, a.marked, a.tags]);
1579
1580                                         }
1581                                 }
1582
1583                                 if (articles_found > 0) {
1584                                         window.setTimeout("initiate_offline_download("+(stage+1)+")", 50);
1585                                 } else {
1586                                         notify_info("All done.");
1587                                         closeInfoBox();
1588                                 }
1589                         }
1590
1591                 }
1592         } catch (e) {
1593                 exception_error("offline_download_parse", e);
1594         }
1595 }
1596
1597 function initiate_offline_download(stage, caller) {
1598         try {
1599
1600                 if (!stage) stage = 0;
1601                 if (caller) caller.disabled = true;
1602
1603                 notify_progress("Loading, please wait... (" + stage +")", true);
1604
1605                 var query = "backend.php?op=rpc&subop=download&stage=" + stage;
1606
1607                 if (stage == 0) {
1608                         var rs = db.execute("SELECT MAX(id) FROM articles");
1609                         if (rs.isValidRow() && rs.field(0)) {
1610                                 offline_dl_max_id = rs.field(0);
1611                         }
1612                 }
1613
1614                 if (offline_dl_max_id) {
1615                         query = query + "&cid=" + offline_dl_max_id;
1616                 }
1617
1618                 if (document.getElementById("download_ops_form")) {
1619                         query = query + "&" + Form.serialize("download_ops_form");
1620                 }
1621
1622                 new Ajax.Request(query, {
1623                         onComplete: function(transport) { 
1624                                 offline_download_parse(stage, transport);                               
1625                         } });
1626
1627         } catch (e) {
1628                 exception_error("initiate_offline_download", e);
1629         }
1630 }