]> git.wh0rd.org Git - tt-rss.git/blob - viewfeed.js
bring back update button, it makes async update request to the daemon
[tt-rss.git] / viewfeed.js
1 var active_post_id = false;
2 var last_article_view = false;
3 var active_real_feed_id = false;
4
5 // FIXME: kludges, needs proper implementation
6 var _reload_feedlist_after_view = false;
7
8 var _cdm_wd_timeout = false;
9 var _cdm_wd_vishist = new Array();
10
11 var article_cache = new Array();
12
13 var vgroup_last_feed = false;
14 var post_under_pointer = false;
15
16 var last_requested_article = false;
17
18 var preload_id_batch = [];
19 var preload_timeout_id = false;
20
21 var cache_added = [];
22
23 function catchup_callback2(transport, callback) {
24         try {
25                 console.log("catchup_callback2 " + transport + ", " + callback);
26                 notify("");                     
27                 handle_rpc_reply(transport);
28                 if (callback) {
29                         setTimeout(callback, 10);       
30                 }
31         } catch (e) {
32                 exception_error("catchup_callback2", e, transport);
33         }
34 }
35
36 function headlines_callback2(transport, feed_cur_page) {
37         try {
38
39                 if (!handle_rpc_reply(transport)) return;
40
41                 loading_set_progress(100);
42
43                 console.log("headlines_callback2 [page=" + feed_cur_page + "]");
44
45                 if (!transport_error_check(transport)) return;
46
47                 clean_feed_selections();
48         
49                 var is_cat = false;
50                 var feed_id = false;
51
52                 if (transport.responseXML) {
53                         var headlines = transport.responseXML.getElementsByTagName("headlines")[0];
54                         if (headlines) {
55                                 is_cat = headlines.getAttribute("is_cat");
56                                 feed_id = headlines.getAttribute("id");
57                                 setActiveFeedId(feed_id, is_cat);
58                         }
59                 }
60
61                 var update_btn = document.forms["main_toolbar_form"].update;
62
63                 update_btn.disabled = !(feed_id >= 0 && !is_cat);
64
65                 var ll = $('FLL-' + feed_id);
66
67                 if (ll && ll.parentNode) 
68                         ll.parentNode.removeChild(ll);
69
70                 if (!is_cat) {
71                         var feedr = $("FEEDR-" + feed_id);
72                         if (feedr && !feedr.className.match("Selected")) {      
73                                 feedr.className = feedr.className + "Selected";
74                         } 
75                 } else {
76                         var feedr = $("FCAT-" + feed_id);
77                         if (feedr && !feedr.className.match("Selected")) {      
78                                 feedr.className = feedr.className + "Selected";
79                         } 
80                 }
81
82                 var img = $('FIMG-' + feed_id);
83
84                 if (img && !is_cat) {
85                         img.src = img.alt;
86                 }
87
88                 var f = $("headlines-frame");
89                 try {
90                         if (feed_cur_page == 0) { 
91                                 //console.log("resetting headlines scrollTop");
92                                 f.scrollTop = 0; 
93                         }
94                 } catch (e) { };
95         
96                 if (transport.responseXML) {
97                         var response = transport.responseXML;
98
99                         var headlines = response.getElementsByTagName("headlines")[0];
100                         var headlines_info = response.getElementsByTagName("headlines-info")[0];
101
102                         if (headlines_info)
103                                 headlines_info = JSON.parse(headlines_info.firstChild.nodeValue);
104                         else {
105                                 console.error("didn't find headlines-info object in response");
106                                 return;
107                         }
108
109                         var headlines_count = headlines_info.count;
110                         var headlines_unread = headlines_info.unread;
111                         var disable_cache = headlines_info.disable_cache;
112                         
113                         vgroup_last_feed = headlines_info.vgroup_last_feed;
114
115                         if (headlines_count == 0) {
116                                 _infscroll_disable = 1;
117                         } else {
118                                 _infscroll_disable = 0;
119                         }
120
121                         var counters = response.getElementsByTagName("counters")[0];
122                         var articles = response.getElementsByTagName("article");
123                         var runtime_info = response.getElementsByTagName("runtime-info");
124         
125                         if (feed_cur_page == 0) {
126                                 if (headlines) {
127                                         f.innerHTML = headlines.firstChild.nodeValue;
128
129                                         var cache_prefix = "";
130
131                                         if (is_cat) {
132                                                 cache_prefix = "C:";
133                                         } else {
134                                                 cache_prefix = "F:";
135                                         }
136
137                                         cache_invalidate(cache_prefix + feed_id);
138
139                                         if (!disable_cache) {
140                                                 cache_inject(cache_prefix + feed_id,
141                                                         headlines.firstChild.nodeValue, headlines_unread);
142                                         }
143
144                                 } else {
145                                         console.warn("headlines_callback: returned no data");
146                                 f.innerHTML = "<div class='whiteBox'>" + __('Could not update headlines (missing XML data)') + "</div>";
147         
148                                 }
149                         } else {
150                                 if (headlines) {
151                                         if (headlines_count > 0) {
152                                                 console.log("adding some more headlines...");
153         
154                                                 c = $("headlinesInnerContainer");
155
156                                                 var ids = getSelectedArticleIds2();
157         
158                                                 c.innerHTML = c.innerHTML + headlines.firstChild.nodeValue;
159
160                                                 console.log("restore selected ids: " + ids);
161
162                                                 for (var i = 0; i < ids.length; i++) {
163                                                         markHeadline(ids[i]);
164                                                 }
165
166                                         } else {
167                                                 console.log("no new headlines received");
168                                         }
169                                 } else {
170                                         console.warn("headlines_callback: returned no data");
171                                         notify_error("Error while trying to load more headlines");      
172                                 }
173
174                         }
175         
176                         if (articles) {
177                                 for (var i = 0; i < articles.length; i++) {
178                                         var a_id = articles[i].getAttribute("id");
179                                         //console.log("found id: " + a_id);
180                                         cache_inject(a_id, articles[i].firstChild.nodeValue);
181                                 }
182                         } else {
183                                 console.log("no cached articles received");
184                         }
185
186                         if (counters)
187                                 parse_counters(counters);
188                         else
189                                 request_counters();
190
191                         if (runtime_info) {
192                                 parse_runtime_info(runtime_info[0]);
193                         } 
194         
195                 } else {
196                         console.warn("headlines_callback: returned no XML object");
197                         f.innerHTML = "<div class='whiteBox'>" + __('Could not update headlines (missing XML object)') + "</div>";
198                 }
199         
200
201                 if (_cdm_wd_timeout) window.clearTimeout(_cdm_wd_timeout);
202
203                 if (isCdmMode() && 
204                                 getActiveFeedId() != -3 &&
205                                 getInitParam("cdm_auto_catchup") == 1) {
206                         console.log("starting CDM watchdog");
207                         _cdm_wd_timeout = window.setTimeout("cdmWatchdog()", 5000);
208                         _cdm_wd_vishist = new Array();
209                 } else {
210                         console.log("not in CDM mode or watchdog disabled");
211                 }
212         
213                 _feed_cur_page = feed_cur_page;
214                 _infscroll_request_sent = 0;
215
216                 notify("");
217
218                 remove_splash();
219
220         } catch (e) {
221                 exception_error("headlines_callback2", e, transport);
222         }
223 }
224
225 function render_article(article) {
226         try {
227                 var f = $("content-frame");
228                 try {
229                         f.scrollTop = 0;
230                 } catch (e) { };
231
232                 var fi = $("content-insert");
233
234                 try {
235                         fi.scrollTop = 0;
236                 } catch (e) { };
237                 
238                 fi.innerHTML = article;
239                 
240 //              article.evalScripts();          
241
242         } catch (e) {
243                 exception_error("render_article", e);
244         }
245 }
246
247 function showArticleInHeadlines(id) {
248
249         try {
250
251                 selectArticles("none");
252
253                 var crow = $("RROW-" + id);
254
255                 if (!crow) return;
256
257                 var article_is_unread = crow.className.match("Unread");
258                         
259                 crow.className = crow.className.replace("Unread", "");
260
261                 selectArticles('none');
262
263                 var upd_img_pic = $("FUPDPIC-" + id);
264
265                 var cache_prefix = "";
266                                 
267                 if (activeFeedIsCat()) {
268                         cache_prefix = "C:";
269                 } else {
270                         cache_prefix = "F:";
271                 }
272
273                 var view_mode = false;
274
275                 try {
276                         view_mode = document.forms['main_toolbar_form'].view_mode;      
277                         view_mode = view_mode[view_mode.selectedIndex].value;
278                 } catch (e) {
279                         //
280                 }
281
282                 if (upd_img_pic && (upd_img_pic.src.match("updated.png") || 
283                                         upd_img_pic.src.match("fresh_sign.png"))) {
284
285                         upd_img_pic.src = "images/blank_icon.gif";
286
287                         cache_invalidate(cache_prefix + getActiveFeedId());
288
289                         cache_inject(cache_prefix + getActiveFeedId(),
290                                 $("headlines-frame").innerHTML,
291                                 get_feed_unread(getActiveFeedId()));
292
293                 } else if (article_is_unread && view_mode == "all_articles") {
294
295                         cache_invalidate(cache_prefix + getActiveFeedId());
296
297                         cache_inject(cache_prefix + getActiveFeedId(),
298                                 $("headlines-frame").innerHTML,
299                                 get_feed_unread(getActiveFeedId())-1);
300
301                 } else if (article_is_unread) {
302                         cache_invalidate(cache_prefix + getActiveFeedId());
303                 }
304
305                 markHeadline(id);
306
307                 if (article_is_unread)
308                         _force_scheduled_update = true;
309
310         } catch (e) {
311                 exception_error("showArticleInHeadlines", e);
312         }
313 }
314
315 function article_callback2(transport, id) {
316         try {
317                 console.log("article_callback2 " + id);
318
319                 if (!handle_rpc_reply(transport)) return;
320
321                 if (transport.responseXML) {
322
323                         if (!transport_error_check(transport)) return;
324
325 /*                      var ll = $('LL-' + id);
326                         var content = $('HLC-' + id);
327
328                         if (ll && content) content.removeChild(ll); */
329                         
330                         var upic = $('FUPDPIC-' + id);
331
332                         if (upic) {
333                                 upic.src = 'images/blank_icon.gif';
334                         }
335
336                         if (id != last_requested_article) {
337                                 console.log("requested article id is out of sequence, aborting");
338                                 return;
339                         }
340
341 //                      active_post_id = id; 
342
343                         //console.log("looking for articles to cache...");
344
345                         var articles = transport.responseXML.getElementsByTagName("article");
346
347                         for (var i = 0; i < articles.length; i++) {
348                                 var a_id = articles[i].getAttribute("id");
349
350                                 //console.log("found id: " + a_id);
351
352                                 if (a_id == active_post_id) {
353                                         //console.log("active article, rendering...");                                  
354                                         render_article(articles[i].firstChild.nodeValue);
355                                 }
356
357                                 cache_inject(a_id, articles[i].firstChild.nodeValue);
358                         }
359
360
361 //                      showArticleInHeadlines(id);     
362
363                         if (db) {
364                                 db.execute("UPDATE articles SET unread = 0 WHERE id = ?", [id]);
365                         }
366
367                         var reply = transport.responseXML.firstChild.firstChild;
368                 
369                 } else {
370                         console.warn("article_callback: returned no XML object");
371                         //var f = $("content-frame");
372                         //f.innerHTML = "<div class='whiteBox'>" + __('Could not display article (missing XML object)') + "</div>";
373                 }
374
375                 var date = new Date();
376                 last_article_view = date.getTime() / 1000;
377
378                 if (_reload_feedlist_after_view) {
379                         setTimeout('updateFeedList(false, false)', 50);                 
380                         _reload_feedlist_after_view = false;
381                 } else {
382                         request_counters();
383                 }
384
385                 notify("");
386         } catch (e) {
387                 exception_error("article_callback2", e, transport);
388         }
389 }
390
391 function view(id) {
392         try {
393                 console.log("loading article: " + id);
394
395                 if (offline_mode) return view_offline(id);
396
397                 var cached_article = cache_find(id);
398
399                 console.log("cache check result: " + (cached_article != false));
400         
401                 enableHotkeys();
402                 hideAuxDlg();
403
404                 var query = "?op=view&id=" + param_escape(id);
405
406                 var neighbor_ids = getRelativePostIds(active_post_id);
407
408                 /* only request uncached articles */
409
410                 var cids_to_request = Array();
411
412                 for (var i = 0; i < neighbor_ids.length; i++) {
413                         if (!cache_check(neighbor_ids[i])) {
414                                 cids_to_request.push(neighbor_ids[i]);
415                         }
416                 }
417
418                 console.log("additional ids: " + cids_to_request.toString());                   
419         
420                 /* additional info for piggyback counters */
421
422                 if (tagsAreDisplayed()) {
423                         query = query + "&omode=lt";
424                 } else {
425                         query = query + "&omode=flc";
426                 }
427
428                 query = query + "&cids=" + cids_to_request.toString();
429
430                 var crow = $("RROW-" + id);
431                 var article_is_unread = crow.className.match("Unread");
432
433                 active_post_id = id;
434                 showArticleInHeadlines(id);
435
436                 if (!cached_article) {
437
438                         var upic = $('FUPDPIC-' + id);
439
440                         if (upic) {     
441                                 upic.src = getInitParam("sign_progress");
442                         }
443
444                 } else if (cached_article && article_is_unread) {
445
446                         query = query + "&mode=prefetch";
447
448                         render_article(cached_article);
449
450                 } else if (cached_article) {
451
452                         query = query + "&mode=prefetch_old";
453                         render_article(cached_article);
454
455                 }
456
457                 cache_expire();
458
459                 last_requested_article = id;
460
461                 new Ajax.Request("backend.php", {
462                         parameters: query,
463                         onComplete: function(transport) { 
464                                 article_callback2(transport, id); 
465                         } });
466
467                 return false;
468
469         } catch (e) {
470                 exception_error("view", e);
471         }
472 }
473
474 function tMark(id) {
475         return toggleMark(id);
476 }
477
478 function tPub(id) {
479         return togglePub(id);
480 }
481
482 function tMark_afh_off(effect) {
483         try {
484                 var elem = effect.effects[0].element;
485
486                 //console.log("tMark_afh_off : " + elem.id);
487
488                 if (elem) {
489                         elem.src = elem.src.replace("mark_set", "mark_unset");
490                         elem.alt = __("Star article");
491                         Element.show(elem);
492                 }
493
494         } catch (e) {
495                 exception_error("tMark_afh_off", e);
496         }
497 }
498
499 function tPub_afh_off(effect) {
500         try {
501                 var elem = effect.effects[0].element;
502
503                 //console.log("tPub_afh_off : " + elem.id);
504
505                 if (elem) {
506                         elem.src = elem.src.replace("pub_set", "pub_unset");
507                         elem.alt = __("Publish article");
508                         Element.show(elem);
509                 }
510
511         } catch (e) {
512                 exception_error("tPub_afh_off", e);
513         }
514 }
515
516 function toggleMark(id, client_only, no_effects) {
517
518         try {
519
520                 var query = "?op=rpc&id=" + id + "&subop=mark";
521         
522                 query = query + "&afid=" + getActiveFeedId();
523         
524                 if (tagsAreDisplayed()) {
525                         query = query + "&omode=tl";
526                 } else {
527                         query = query + "&omode=flc";
528                 }
529         
530                 var mark_img = $("FMPIC-" + id);
531
532                 if (!mark_img) return;
533
534                 var vfeedu = $("FEEDU--1");
535                 var crow = $("RROW-" + id);
536         
537                 if (mark_img.src.match("mark_unset")) {
538                         mark_img.src = mark_img.src.replace("mark_unset", "mark_set");
539                         mark_img.alt = __("Unstar article");
540                         query = query + "&mark=1";
541
542                         if (db) {
543                                 db.execute("UPDATE articles SET marked = 1 WHERE id = ?", [id]);
544                         }
545
546                 } else {
547                         mark_img.alt = __("Please wait...");
548                         query = query + "&mark=0";
549         
550                         mark_img.src = mark_img.src.replace("mark_set", "mark_unset");
551                         mark_img.alt = __("Star article");
552
553                         if (db) {
554                                 db.execute("UPDATE articles SET marked = 0 WHERE id = ?", [id]);
555                         }
556
557                 }
558
559                 if (!no_effects) update_local_feedlist_counters();
560
561                 if (!client_only) {
562                         //console.log(query);
563
564                         new Ajax.Request("backend.php", {
565                                 parameters: query,
566                                 onComplete: function(transport) { 
567                                         handle_rpc_reply(transport); 
568                                 } });
569
570                 }
571
572         } catch (e) {
573                 exception_error("toggleMark", e);
574         }
575 }
576
577 function togglePub(id, client_only, no_effects, note) {
578
579         try {
580
581                 var query = "?op=rpc&id=" + id + "&subop=publ";
582         
583                 query = query + "&afid=" + getActiveFeedId();
584
585                 if (note != undefined) {
586                         query = query + "&note=" + param_escape(note);
587                 } else {
588                         query = query + "&note=undefined";
589                 }
590         
591                 if (tagsAreDisplayed()) {
592                         query = query + "&omode=tl";
593                 } else {
594                         query = query + "&omode=flc";
595                 }
596         
597                 var mark_img = $("FPPIC-" + id);
598
599                 if (!mark_img) return;
600
601                 var vfeedu = $("FEEDU--2");
602                 var crow = $("RROW-" + id);
603         
604                 if (mark_img.src.match("pub_unset") || note != undefined) {
605                         mark_img.src = mark_img.src.replace("pub_unset", "pub_set");
606                         mark_img.alt = __("Unpublish article");
607                         query = query + "&pub=1";
608
609                 } else {
610                         mark_img.alt = __("Please wait...");
611                         query = query + "&pub=0";
612         
613                         mark_img.src = mark_img.src.replace("pub_set", "pub_unset");
614                         mark_img.alt = __("Publish article");
615
616                 }
617
618                 if (!client_only) {
619                         new Ajax.Request("backend.php", {
620                                 parameters: query,
621                                 onComplete: function(transport) { 
622                                         handle_rpc_reply(transport);
623                 
624                                         var note = transport.responseXML.getElementsByTagName("note")[0];
625                 
626                                         if (note) {
627                                                 var note_id = note.getAttribute("id");
628                                                 var note_size = note.getAttribute("size");
629                                                 var note_content = note.firstChild.nodeValue;
630                 
631                                                 var container = $('POSTNOTE-' + note_id);
632                 
633                                                 cache_invalidate(note_id);
634                 
635                                                 if (container) {
636                                                         if (note_size == "0") {
637                                                                 Element.hide(container);
638                                                         } else {
639                                                                 container.innerHTML = note_content;
640                                                                 Element.show(container);
641                                                         }
642                                                 }
643                                         }       
644
645                                 } });
646                 }
647
648         } catch (e) {
649                 exception_error("togglePub", e);
650         }
651 }
652
653 function moveToPost(mode) {
654
655         try {
656
657                 var rows = getVisibleArticleIds();
658
659                 var prev_id = false;
660                 var next_id = false;
661                 
662                 if (!$('RROW-' + active_post_id)) {
663                         active_post_id = false;
664                 }
665                 
666                 if (active_post_id == false) {
667                         next_id = getFirstVisibleHeadlineId();
668                         prev_id = getLastVisibleHeadlineId();
669                 } else {        
670                         for (var i = 0; i < rows.length; i++) {
671                                 if (rows[i] == active_post_id) {
672                                         prev_id = rows[i-1];
673                                         next_id = rows[i+1];                    
674                                 }
675                         }
676                 }
677                 
678                 if (mode == "next") {
679                         if (next_id) {
680                                 if (isCdmMode()) {
681         
682                                         cdmExpandArticle(next_id);
683                                         cdmScrollToArticleId(next_id);
684
685                                 } else {
686                                         correctHeadlinesOffset(next_id);
687                                         view(next_id, getActiveFeedId());
688                                 }
689                         }
690                 }
691                 
692                 if (mode == "prev") {
693                         if (prev_id) {
694                                 if (isCdmMode()) {
695                                         cdmExpandArticle(prev_id);
696                                         cdmScrollToArticleId(prev_id);
697                                 } else {
698                                         correctHeadlinesOffset(prev_id);
699                                         view(prev_id, getActiveFeedId());
700                                 }
701                         }
702                 } 
703
704         } catch (e) {
705                 exception_error("moveToPost", e);
706         }
707 }
708
709 function toggleSelected(id) {
710         try {
711         
712                 var cb = $("RCHK-" + id);
713
714                 var row = $("RROW-" + id);
715                 if (row) {
716                         var nc = row.className;
717                         
718                         if (!nc.match("Selected")) {
719                                 nc = nc + "Selected";
720                                 if (cb) {
721                                         cb.checked = true;
722                                 }
723
724                                 // In CDM basically last selected article == active article
725                                 if (isCdmMode()) active_post_id = id;
726                         } else {
727                                 nc = nc.replace("Selected", "");
728                                 if (cb) {
729                                         cb.checked = false;
730                                 }
731
732                         }
733
734                         row.className = nc;
735                 }
736         } catch (e) {
737                 exception_error("toggleSelected", e);
738         }
739 }
740
741 function toggleUnread_afh(effect) {
742         try {
743
744                 var elem = effect.element;
745                 elem.style.backgroundColor = "";
746
747         } catch (e) {
748                 exception_error("toggleUnread_afh", e);
749         }
750
751
752 function toggleUnread(id, cmode, effect) {
753         try {
754         
755                 var row = $("RROW-" + id);
756                 if (row) {
757                         var nc = row.className;
758                         var is_selected = row.className.match("Selected");
759                         nc = nc.replace("Unread", "");
760                         nc = nc.replace("Selected", "");
761
762                         // since we are removing selection from the object, uncheck
763                         // corresponding checkbox
764
765                         var cb = $("RCHK-" + id);
766                         if (cb) {
767                                 cb.checked = false;
768                         }
769
770                         // NOTE: I'm not sure that resetting selection here is a feature -fox
771
772                         if (cmode == undefined || cmode == 2) {
773                                 if (row.className.match("Unread")) {
774                                         row.className = nc;
775
776                                         if (effect) {
777                                                 new Effect.Highlight(row, {duration: 1, startcolor: "#fff7d5",
778                                                         afterFinish: toggleUnread_afh,
779                                                         queue: { position:'end', scope: 'TMRQ-' + id, limit: 1 } } );
780                                         } 
781
782                                 } else {
783                                         row.className = nc + "Unread";
784                                 }
785
786                                 if (db) {
787                                         db.execute("UPDATE articles SET unread = not unread "+
788                                                 "WHERE id = ?", [id]);
789                                 }
790
791                         } else if (cmode == 0) {
792                                 row.className = nc;
793
794                                 if (effect) {
795                                         new Effect.Highlight(row, {duration: 1, startcolor: "#fff7d5",
796                                                 afterFinish: toggleUnread_afh,
797                                                 queue: { position:'end', scope: 'TMRQ-' + id, limit: 1 } } );
798                                 } 
799
800                                 if (db) {
801                                         db.execute("UPDATE articles SET unread = 0 "+
802                                                 "WHERE id = ?", [id]);
803                                 }
804
805                         } else if (cmode == 1) {
806                                 row.className = nc + "Unread";
807
808                                 if (db) {
809                                         db.execute("UPDATE articles SET unread = 1 "+
810                                                 "WHERE id = ?", [id]);
811                                 }
812
813                         }
814
815                         update_local_feedlist_counters();
816
817                         // Disable unmarking as selected for the time being (16.05.08) -fox
818                         if (is_selected) row.className = row.className + "Selected";
819
820                         if (cmode == undefined) cmode = 2;
821
822                         var query = "?op=rpc&subop=catchupSelected" +
823                                 "&cmode=" + param_escape(cmode) + "&ids=" + param_escape(id);
824
825 //                      notify_progress("Loading, please wait...");
826
827                         new Ajax.Request("backend.php", {
828                                 parameters: query,
829                                 onComplete: function(transport) { 
830                                         handle_rpc_reply(transport); 
831                                 } });
832
833                 }
834
835         } catch (e) {
836                 exception_error("toggleUnread", e);
837         }
838 }
839
840 function selectionRemoveLabel(id) {
841         try {
842
843                 var ids = getSelectedArticleIds2();
844
845                 if (ids.length == 0) {
846                         alert(__("No articles are selected."));
847                         return;
848                 }
849
850 //              var ok = confirm(__("Remove selected articles from label?"));
851
852 //              if (ok) {
853
854                         var query = "?op=rpc&subop=removeFromLabel&ids=" +
855                                 param_escape(ids.toString()) + "&lid=" + param_escape(id);
856
857                         console.log(query);
858
859 //                      notify_progress("Loading, please wait...");
860
861                         cache_invalidate("F:" + (-11 - id));
862
863                         new Ajax.Request("backend.php", {
864                                 parameters: query,
865                                 onComplete: function(transport) { 
866                                         show_labels_in_headlines(transport);
867                                         handle_rpc_reply(transport);
868                                 } });
869
870 //              }
871
872         } catch (e) {
873                 exception_error("selectionAssignLabel", e);
874
875         }
876 }
877
878 function selectionAssignLabel(id) {
879         try {
880
881                 var ids = getSelectedArticleIds2();
882
883                 if (ids.length == 0) {
884                         alert(__("No articles are selected."));
885                         return;
886                 }
887
888 //              var ok = confirm(__("Assign selected articles to label?"));
889
890 //              if (ok) {
891
892                         cache_invalidate("F:" + (-11 - id));
893
894                         var query = "?op=rpc&subop=assignToLabel&ids=" +
895                                 param_escape(ids.toString()) + "&lid=" + param_escape(id);
896
897                         console.log(query);
898
899 //                      notify_progress("Loading, please wait...");
900
901                         new Ajax.Request("backend.php", {
902                                 parameters: query,
903                                 onComplete: function(transport) { 
904                                         show_labels_in_headlines(transport);
905                                         handle_rpc_reply(transport);
906                                 } });
907
908 //              }
909
910         } catch (e) {
911                 exception_error("selectionAssignLabel", e);
912
913         }
914 }
915
916 function selectionToggleUnread(set_state, callback_func, no_error) {
917         try {
918                 var rows = getSelectedArticleIds2();
919
920                 if (rows.length == 0 && !no_error) {
921                         alert(__("No articles are selected."));
922                         return;
923                 }
924
925                 for (i = 0; i < rows.length; i++) {
926                         var row = $("RROW-" + rows[i]);
927                         if (row) {
928                                 var nc = row.className;
929                                 nc = nc.replace("Unread", "");
930                                 nc = nc.replace("Selected", "");
931
932                                 if (set_state == undefined) {
933                                         if (row.className.match("Unread")) {
934                                                 row.className = nc + "Selected";
935                                         } else {
936                                                 row.className = nc + "UnreadSelected";
937                                         }
938                                         if (db) {
939                                                 db.execute("UPDATE articles SET unread = NOT unread WHERE id = ?", 
940                                                         [rows[i]]);
941                                         }
942                                 }
943
944                                 if (set_state == false) {
945                                         row.className = nc + "Selected";
946                                         if (db) {
947                                                 db.execute("UPDATE articles SET unread = 0 WHERE id = ?", 
948                                                         [rows[i]]);
949                                         }
950                                 }
951
952                                 if (set_state == true) {
953                                         row.className = nc + "UnreadSelected";
954                                         if (db) {
955                                                 db.execute("UPDATE articles SET unread = 1 WHERE id = ?", 
956                                                         [rows[i]]);
957                                         }
958                                 }
959                         }
960                 }
961
962                 if (rows.length > 0) {
963
964                         update_local_feedlist_counters();
965
966                         var cmode = "";
967
968                         if (set_state == undefined) {
969                                 cmode = "2";
970                         } else if (set_state == true) {
971                                 cmode = "1";
972                         } else if (set_state == false) {
973                                 cmode = "0";
974                         }
975
976                         var query = "?op=rpc&subop=catchupSelected" +
977                                 "&cmode=" + cmode + "&ids=" + param_escape(rows.toString()); 
978
979                         notify_progress("Loading, please wait...");
980
981                         new Ajax.Request("backend.php", {
982                                 parameters: query,
983                                 onComplete: function(transport) { 
984                                         catchup_callback2(transport, callback_func); 
985                                 } });
986
987                 }
988
989         } catch (e) {
990                 exception_error("selectionToggleUnread", e);
991         }
992 }
993
994 function selectionToggleMarked() {
995         try {
996         
997                 var rows = getSelectedArticleIds2();
998                 
999                 if (rows.length == 0) {
1000                         alert(__("No articles are selected."));
1001                         return;
1002                 }
1003
1004                 for (i = 0; i < rows.length; i++) {
1005                         toggleMark(rows[i], true, true);
1006                 }
1007
1008                 update_local_feedlist_counters();
1009
1010                 if (rows.length > 0) {
1011
1012                         var query = "?op=rpc&subop=markSelected&ids=" +
1013                                 param_escape(rows.toString()) + "&cmode=2";
1014
1015                         query = query + "&afid=" + getActiveFeedId();
1016
1017                         query = query + "&omode=lc";
1018
1019                         new Ajax.Request("backend.php", {
1020                                 parameters: query,
1021                                 onComplete: function(transport) { 
1022                                         handle_rpc_reply(transport); 
1023                                 } });
1024
1025                 }
1026
1027         } catch (e) {
1028                 exception_error("selectionToggleMarked", e);
1029         }
1030 }
1031
1032 function selectionTogglePublished() {
1033         try {
1034         
1035                 var rows = getSelectedArticleIds2();
1036
1037                 if (rows.length == 0) {
1038                         alert(__("No articles are selected."));
1039                         return;
1040                 }
1041
1042                 for (i = 0; i < rows.length; i++) {
1043                         togglePub(rows[i], true, true);
1044                 }
1045
1046                 if (rows.length > 0) {
1047
1048                         var query = "?op=rpc&subop=publishSelected&ids=" +
1049                                 param_escape(rows.toString()) + "&cmode=2";
1050
1051                         query = query + "&afid=" + getActiveFeedId();
1052
1053                         query = query + "&omode=lc";
1054
1055                         new Ajax.Request("backend.php", {
1056                                 parameters: query,
1057                                 onComplete: function(transport) { 
1058                                         handle_rpc_reply(transport); 
1059                                 } });
1060
1061                 }
1062
1063         } catch (e) {
1064                 exception_error("selectionToggleMarked", e);
1065         }
1066 }
1067
1068 function getSelectedArticleIds2() {
1069         var sel_articles = new Array();
1070
1071         var children;
1072         
1073         var children = $("headlinesInnerContainer").childNodes;
1074
1075         for (i = 0; i < children.length; i++) {
1076                 var child = children[i];
1077
1078                 if (child.id && child.id.match("RROW-") && child.className.match("Selected")) {
1079                         var c_id = child.id.replace("RROW-", "");
1080                         sel_articles.push(c_id);
1081                 }
1082         }
1083
1084         return sel_articles;
1085 }
1086
1087 function getLoadedArticleIds() {
1088         var sel_articles = new Array();
1089
1090         var children = $("headlinesInnerContainer").childNodes;
1091
1092         if (!children) return sel_articles;
1093
1094         for (i = 0; i < children.length; i++) {
1095                 var child = children[i];
1096
1097                 if (child.id && child.id.match("RROW-")) {
1098                         var c_id = child.id.replace("RROW-", "");
1099                         sel_articles.push(c_id);
1100                 }
1101         }
1102
1103         return sel_articles;
1104 }
1105
1106 // mode = all,none,unread,invert
1107 function selectArticles(mode) {
1108         try {
1109
1110                 var children;
1111         
1112                 var children = $("headlinesInnerContainer").childNodes;
1113
1114                 for (i = 0; i < children.length; i++) {
1115                         var child = children[i];
1116         
1117                         if (child.id && child.id.match("RROW-")) {
1118                                 var aid = child.id.replace("RROW-", "");
1119         
1120                                 var cb = $("RCHK-" + aid);
1121         
1122                                 if (mode == "all") {
1123                                         if (!child.className.match("Selected")) {
1124                                                 child.className = child.className + "Selected";
1125                                                 cb.checked = true;
1126                                         }
1127                                 } else if (mode == "unread") {
1128                                         if (child.className.match("Unread") && !child.className.match("Selected")) {
1129                                                 child.className = child.className + "Selected";
1130                                                 cb.checked = true;
1131                                         }
1132                                 } else if (mode == "invert") {
1133                                         if (child.className.match("Selected")) {
1134                                                 child.className = child.className.replace("Selected", "");
1135                                                 cb.checked = false;
1136                                         } else {
1137                                                 child.className = child.className + "Selected";
1138                                                 cb.checked = true;
1139                                         }
1140
1141                                 } else {
1142                                         child.className = child.className.replace("Selected", "");
1143                                         cb.checked = false;
1144                                 }
1145                         }               
1146                 }
1147
1148         } catch (e) {
1149                 exception_error("selectArticles", e);
1150         }
1151 }
1152
1153 function catchupPage() {
1154
1155         var fn = getFeedName(getActiveFeedId(), activeFeedIsCat());
1156         
1157         var str = __("Mark all visible articles in %s as read?");
1158
1159         str = str.replace("%s", fn);
1160
1161         if (getInitParam("confirm_feed_catchup") == 1 && !confirm(str)) {
1162                 return;
1163         }
1164
1165         selectArticles('all');
1166         selectionToggleUnread(false, 'viewCurrentFeed()', true)
1167         selectArticles('none');
1168 }
1169
1170 function deleteSelection() {
1171
1172         try {
1173         
1174                 var rows = getSelectedArticleIds2();
1175
1176                 if (rows.length == 0) {
1177                         alert(__("No articles are selected."));
1178                         return;
1179                 }
1180         
1181                 var fn = getFeedName(getActiveFeedId(), activeFeedIsCat());
1182                 var str;
1183                 var op;
1184         
1185                 if (getActiveFeedId() != 0) {
1186                         str = __("Delete %d selected articles in %s?");
1187                 } else {
1188                         str = __("Delete %d selected articles?");
1189                 }
1190         
1191                 str = str.replace("%d", rows.length);
1192                 str = str.replace("%s", fn);
1193         
1194                 if (getInitParam("confirm_feed_catchup") == 1 && !confirm(str)) {
1195                         return;
1196                 }
1197
1198                 query = "?op=rpc&subop=delete&ids=" + param_escape(rows);
1199
1200                 console.log(query);
1201
1202                 new Ajax.Request("backend.php", {
1203                         parameters: query,
1204                         onComplete: function(transport) {
1205                                         viewCurrentFeed();
1206                                 } });
1207
1208         } catch (e) {
1209                 exception_error("deleteSelection", e);
1210         }
1211 }
1212
1213 function archiveSelection() {
1214
1215         try {
1216
1217                 var rows = getSelectedArticleIds2();
1218
1219                 if (rows.length == 0) {
1220                         alert(__("No articles are selected."));
1221                         return;
1222                 }
1223         
1224                 var fn = getFeedName(getActiveFeedId(), activeFeedIsCat());
1225                 var str;
1226                 var op;
1227         
1228                 if (getActiveFeedId() != 0) {
1229                         str = __("Archive %d selected articles in %s?");
1230                         op = "archive";
1231                 } else {
1232                         str = __("Move %d archived articles back?");
1233                         op = "unarchive";
1234                 }
1235         
1236                 str = str.replace("%d", rows.length);
1237                 str = str.replace("%s", fn);
1238         
1239                 if (getInitParam("confirm_feed_catchup") == 1 && !confirm(str)) {
1240                         return;
1241                 }
1242
1243                 query = "?op=rpc&subop="+op+"&ids=" + param_escape(rows);
1244
1245                 console.log(query);
1246
1247                 for (var i = 0; i < rows.length; i++) {
1248                         cache_invalidate(rows[i]);
1249                 }
1250
1251                 new Ajax.Request("backend.php", {
1252                         parameters: query,
1253                         onComplete: function(transport) {
1254                                         viewCurrentFeed();
1255                                 } });
1256
1257         } catch (e) {
1258                 exception_error("archiveSelection", e);
1259         }
1260 }
1261
1262 function catchupSelection() {
1263
1264         try {
1265
1266                 var rows = getSelectedArticleIds2();
1267
1268                 if (rows.length == 0) {
1269                         alert(__("No articles are selected."));
1270                         return;
1271                 }
1272         
1273                 var fn = getFeedName(getActiveFeedId(), activeFeedIsCat());
1274                 
1275                 var str = __("Mark %d selected articles in %s as read?");
1276         
1277                 str = str.replace("%d", rows.length);
1278                 str = str.replace("%s", fn);
1279         
1280                 if (getInitParam("confirm_feed_catchup") == 1 && !confirm(str)) {
1281                         return;
1282                 }
1283         
1284                 selectionToggleUnread(false, 'viewCurrentFeed()', true)
1285
1286         } catch (e) {
1287                 exception_error("catchupSelection", e);
1288         }
1289 }
1290
1291 function editArticleTags(id, feed_id, cdm_enabled) {
1292         displayDlg('editArticleTags', id,
1293                            function () {
1294                                         $("tags_str").focus();
1295
1296                                         new Ajax.Autocompleter('tags_str', 'tags_choices',
1297                                            "backend.php?op=rpc&subop=completeTags",
1298                                            { tokens: ',', paramName: "search" });
1299                            });
1300 }
1301
1302 function editTagsSave() {
1303
1304         notify_progress("Saving article tags...");
1305
1306         var form = document.forms["tag_edit_form"];
1307
1308         var query = Form.serialize("tag_edit_form");
1309
1310         query = "?op=rpc&subop=setArticleTags&" + query;
1311
1312         //console.log(query);
1313
1314         new Ajax.Request("backend.php", {
1315                 parameters: query,
1316                 onComplete: function(transport) {
1317                                 try {
1318                                         //console.log("tags saved...");
1319                         
1320                                         closeInfoBox();
1321                                         notify("");
1322                         
1323                                         if (tagsAreDisplayed()) {
1324                                                 _reload_feedlist_after_view = true;
1325                                         }                       
1326                         
1327                                         if (transport.responseXML) {
1328                                                 var tags_str = transport.responseXML.getElementsByTagName("tags-str")[0];
1329                                                 
1330                                                 if (tags_str) {
1331                                                         var id = tags_str.getAttribute("id");
1332                         
1333                                                         if (id) {
1334                                                                 var tags = $("ATSTR-" + id);
1335                                                                 if (tags) {
1336                                                                         tags.innerHTML = tags_str.firstChild.nodeValue;
1337                                                                 }
1338
1339                                                                 cache_invalidate(id);
1340                                                         }
1341                                                 }
1342                                         }
1343                         
1344                                 } catch (e) {
1345                                         exception_error("editTagsSave", e);
1346                                 }
1347                         } });
1348 }
1349
1350 function editTagsInsert() {
1351         try {
1352
1353                 var form = document.forms["tag_edit_form"];
1354
1355                 var found_tags = form.found_tags;
1356                 var tags_str = form.tags_str;
1357
1358                 var tag = found_tags[found_tags.selectedIndex].value;
1359
1360                 if (tags_str.value.length > 0 && 
1361                                 tags_str.value.lastIndexOf(", ") != tags_str.value.length - 2) {
1362
1363                         tags_str.value = tags_str.value + ", ";
1364                 }
1365
1366                 tags_str.value = tags_str.value + tag + ", ";
1367
1368                 found_tags.selectedIndex = 0;
1369                 
1370         } catch (e) {
1371                 exception_error("editTagsInsert", e);
1372         }
1373 }
1374
1375 function cdmScrollToArticleId(id) {
1376         try {
1377                 var ctr = $("headlinesInnerContainer");
1378                 var e = $("RROW-" + id);
1379
1380                 if (!e || !ctr) return;
1381
1382                 ctr.scrollTop = e.offsetTop;
1383
1384         } catch (e) {
1385                 exception_error("cdmScrollToArticleId", e);
1386         }
1387 }
1388
1389 function cdmWatchdog() {
1390
1391         try {
1392
1393                 var ctr = $("headlinesInnerContainer");
1394
1395                 if (!ctr) return;
1396
1397                 var ids = new Array();
1398
1399                 var e = ctr.firstChild;
1400
1401                 while (e) {
1402                         if (e.className && e.className == "cdmArticleUnread" && e.id &&
1403                                         e.id.match("RROW-")) {
1404
1405                                 // article fits in viewport OR article is longer than viewport and
1406                                 // its bottom is visible
1407
1408                                 if (ctr.scrollTop <= e.offsetTop && e.offsetTop + e.offsetHeight <=
1409                                                 ctr.scrollTop + ctr.offsetHeight) {
1410
1411 //                                      console.log(e.id + " is visible " + e.offsetTop + "." + 
1412 //                                              (e.offsetTop + e.offsetHeight) + " vs " + ctr.scrollTop + "." +
1413 //                                              (ctr.scrollTop + ctr.offsetHeight));
1414
1415                                         ids.push(e.id.replace("RROW-", ""));
1416
1417                                 } else if (e.offsetHeight > ctr.offsetHeight &&
1418                                                 e.offsetTop + e.offsetHeight >= ctr.scrollTop &&
1419                                                 e.offsetTop + e.offsetHeight <= ctr.scrollTop + ctr.offsetHeight) {
1420
1421                                         ids.push(e.id.replace("RROW-", "")); 
1422
1423                                 }
1424
1425                                 // method 2: article bottom is visible and is in upper 1/2 of the viewport
1426
1427 /*                              if (e.offsetTop + e.offsetHeight >= ctr.scrollTop &&
1428                                                 e.offsetTop + e.offsetHeight <= ctr.scrollTop + ctr.offsetHeight/2) {
1429
1430                                         ids.push(e.id.replace("RROW-", "")); 
1431
1432                                 } */
1433
1434                         }
1435
1436                         e = e.nextSibling;
1437                 }
1438
1439                 console.log("cdmWatchdog, ids= " + ids.toString());
1440
1441                 if (ids.length > 0) {
1442
1443                         for (var i = 0; i < ids.length; i++) {
1444                                 var e = $("RROW-" + ids[i]);
1445                                 if (e) {
1446                                         e.className = e.className.replace("Unread", "");
1447                                 }
1448                         }
1449
1450                         var query = "?op=rpc&subop=catchupSelected" +
1451                                 "&cmode=0" + "&ids=" + param_escape(ids.toString());
1452
1453                         new Ajax.Request("backend.php", {
1454                                 parameters: query,
1455                                 onComplete: function(transport) { 
1456                                         handle_rpc_reply(transport); 
1457                                 } });
1458
1459                 }
1460
1461                 _cdm_wd_timeout = window.setTimeout("cdmWatchdog()", 4000);
1462
1463         } catch (e) {
1464                 exception_error("cdmWatchdog", e);
1465         }
1466
1467 }
1468
1469
1470 function cache_inject(id, article, param) {
1471
1472         try {
1473                 if (!cache_check_param(id, param)) {
1474                         //console.log("cache_article: miss: " + id + " [p=" + param + "]");
1475
1476                    var date = new Date();
1477               var ts = Math.round(date.getTime() / 1000);
1478
1479                         if (db) {
1480
1481                                 db.execute("INSERT INTO cache (id, article, param, added) VALUES (?, ?, ?, ?)",
1482                                         [id, article, param, ts]);                              
1483                         } else {
1484         
1485                                 var cache_obj = {};
1486         
1487                                 cache_obj["id"] = id;
1488                                 cache_obj["data"] = article;
1489                                 cache_obj["param"] = param;
1490
1491                                 if (param) id = id + ":" + param;
1492
1493                                 cache_added["TS:" + id] = ts;
1494
1495                                 if (has_local_storage()) 
1496                                         localStorage.setItem(id, JSON.stringify(cache_obj));
1497                                 else
1498                                         article_cache.push(cache_obj);
1499                         }
1500         
1501                 } else {
1502                         //console.log("cache_article: hit: " + id + " [p=" + param + "]");
1503                 }
1504         } catch (e) {   
1505                 exception_error("cache_inject", e);
1506         }
1507 }
1508
1509 function cache_find(id) {
1510
1511         if (db) {
1512                 var rs = db.execute("SELECT article FROM cache WHERE id = ?", [id]);
1513                 var a = false;
1514
1515                 if (rs.isValidRow()) {
1516                         var a = rs.field(0);                    
1517                 }
1518
1519                 rs.close();
1520
1521                 return a;
1522
1523         } else {
1524
1525                 if (has_local_storage()) {
1526                         var cache_obj = localStorage.getItem(id);
1527         
1528                         if (cache_obj) {
1529                                 cache_obj = JSON.parse(cache_obj);
1530
1531                                 if (cache_obj)
1532                                         return cache_obj['data'];
1533                         }
1534
1535                 } else {
1536                         for (var i = 0; i < article_cache.length; i++) {
1537                                 if (article_cache[i]["id"] == id) {
1538                                         return article_cache[i]["data"];
1539                                 }
1540                         }
1541                 }
1542         }
1543         return false;
1544 }
1545
1546 function cache_find_param(id, param) {
1547
1548         if (db) {
1549                 var rs = db.execute("SELECT article FROM cache WHERE id = ? AND param = ?",
1550                         [id, param]);
1551                 var a = false;
1552
1553                 if (rs.isValidRow()) {
1554                         a = rs.field(0);
1555                 }
1556
1557                 rs.close();
1558
1559                 return a;
1560
1561         } else {
1562
1563                 if (has_local_storage()) {
1564
1565                         if (param) id = id + ":" + param;
1566
1567                         var cache_obj = localStorage.getItem(id);
1568
1569                         if (cache_obj) {
1570                                 cache_obj = JSON.parse(cache_obj);
1571
1572                                 if (cache_obj)
1573                                         return cache_obj['data'];
1574                         }
1575
1576                 } else {
1577                         for (var i = 0; i < article_cache.length; i++) {
1578                                 if (article_cache[i]["id"] == id && article_cache[i]["param"] == param) {
1579                                         return article_cache[i]["data"];
1580                                 }
1581                         }
1582                 }
1583         }
1584         return false;
1585 }
1586
1587 function cache_check(id) {
1588
1589         if (db) {
1590                 var rs = db.execute("SELECT COUNT(*) AS c FROM cache WHERE id = ?",
1591                         [id]);
1592                 var a = false;
1593
1594                 if (rs.isValidRow()) {
1595                          a = rs.field(0) != "0";
1596                 }
1597
1598                 rs.close();
1599
1600                 return a;
1601
1602         } else {
1603                 if (has_local_storage()) {
1604                         if (localStorage.getItem(id))
1605                                 return true;
1606                 } else {
1607                         for (var i = 0; i < article_cache.length; i++) {
1608                                 if (article_cache[i]["id"] == id) {
1609                                         return true;
1610                                 }
1611                         }
1612                 }
1613         }
1614         return false;
1615 }
1616
1617 function cache_check_param(id, param) {
1618
1619         if (db) {
1620                 var rs = db.execute("SELECT COUNT(*) AS c FROM cache WHERE id = ? AND param = ?",
1621                         [id, param]);
1622                 var a = false;
1623
1624                 if (rs.isValidRow()) {
1625                         a = rs.field(0) != "0";
1626                 }
1627
1628                 rs.close();
1629
1630                 return a;
1631
1632         } else {
1633
1634                 if (has_local_storage()) {
1635
1636                         if (param) id = id + ":" + param;
1637
1638                         if (localStorage.getItem(id))
1639                                 return true;
1640
1641                 } else {
1642                         for (var i = 0; i < article_cache.length; i++) {
1643                                 if (article_cache[i]["id"] == id && article_cache[i]["param"] == param) {
1644                                         return true;
1645                                 }
1646                         }
1647                 }
1648         }
1649         return false;
1650 }
1651
1652 function cache_expire() {
1653         if (db) {
1654                 var date = new Date();
1655                 var ts = Math.round(date.getTime() / 1000);
1656
1657                 db.execute("DELETE FROM cache WHERE added < ? - 1800 AND id LIKE 'FEEDLIST'", [ts]);
1658                 db.execute("DELETE FROM cache WHERE added < ? - 600 AND (id LIKE 'F:%' OR id LIKE 'C:%')", [ts]);
1659                 db.execute("DELETE FROM cache WHERE added < ? - 86400", [ts]);
1660
1661
1662         } else {
1663                 if (has_local_storage()) {
1664
1665                         var date = new Date();
1666                         var timestamp = Math.round(date.getTime() / 1000);
1667
1668                         for (var i = 0; i < localStorage.length; i++) {
1669
1670                                 var id = localStorage.key(i);
1671
1672                                 if (timestamp - cache_added["TS:" + id] > 180) {
1673                                         localStorage.removeItem(id);
1674                                 }
1675                         }
1676
1677                 } else {
1678                         while (article_cache.length > 25) {
1679                                 article_cache.shift();
1680                         }
1681                 }
1682         }
1683 }
1684
1685 function cache_flush() {
1686         if (db) {
1687                 db.execute("DELETE FROM cache");
1688         } else if (has_local_storage()) {
1689                 localStorage.clear();
1690         } else {
1691                 article_cache = new Array();
1692         }
1693 }
1694
1695 function cache_invalidate(id) {
1696         try {   
1697
1698                 if (db) {
1699                         rs = db.execute("DELETE FROM cache WHERE id = ?", [id]);
1700                         return rs.rowsAffected != 0;
1701                 } else {
1702
1703                         if (has_local_storage()) {
1704
1705                                 var found = false;
1706
1707                                 for (var i = 0; i < localStorage.length; i++) {
1708                                         var key = localStorage.key(i);
1709
1710 //                                      console.warn("cache_invalidate: " + key_id + " cmp " + id);
1711
1712                                         if (key == id || key.indexOf(id + ":") == 0) {
1713                                                 localStorage.removeItem(key);
1714                                                 found = true;
1715                                                 break;
1716                                         }
1717                                 }
1718
1719                                 return found;
1720
1721                         } else {
1722                                 var i = 0
1723
1724                                 while (i < article_cache.length) {
1725                                         if (article_cache[i]["id"] == id) {
1726                                                 //console.log("cache_invalidate: removed id " + id);
1727                                                 article_cache.splice(i, 1);
1728                                                 return true;
1729                                         }
1730                                         i++;
1731                                 }
1732                         }
1733                 }
1734
1735                 //console.log("cache_invalidate: id not found: " + id);
1736                 return false;
1737         } catch (e) {
1738                 exception_error("cache_invalidate", e);
1739         }
1740 }
1741
1742 function getActiveArticleId() {
1743         return active_post_id;
1744 }
1745
1746 function preloadBatchedArticles() {
1747         try {
1748
1749                 var query = "?op=rpc&subop=getArticles&ids=" + 
1750                         preload_id_batch.toString();
1751
1752                 new Ajax.Request("backend.php", {
1753                         parameters: query,
1754                         onComplete: function(transport) { 
1755
1756                                 preload_id_batch = [];
1757
1758                                 var articles = transport.responseXML.getElementsByTagName("article");
1759
1760                                 for (var i = 0; i < articles.length; i++) {
1761                                         var id = articles[i].getAttribute("id");
1762                                         if (!cache_check(id)) {
1763                                                 cache_inject(id, articles[i].firstChild.nodeValue);                             
1764                                                 console.log("preloaded article: " + id);
1765                                         }
1766                                 }
1767                 } }); 
1768
1769         } catch (e) {
1770                 exception_error("preloadBatchedArticles", e);
1771         }
1772 }
1773
1774 function preloadArticleUnderPointer(id) {
1775         try {
1776                 if (getInitParam("bw_limit") == "1") return;
1777
1778                 if (post_under_pointer == id && !cache_check(id)) {
1779
1780                         console.log("trying to preload article " + id);
1781
1782                         var neighbor_ids = getRelativePostIds(id, 1);
1783
1784                         /* only request uncached articles */
1785
1786                         if (preload_id_batch.indexOf(id) == -1) {
1787                                 for (var i = 0; i < neighbor_ids.length; i++) {
1788                                         if (!cache_check(neighbor_ids[i])) {
1789                                                 preload_id_batch.push(neighbor_ids[i]);
1790                                         }
1791                                 }
1792                         }
1793
1794                         if (preload_id_batch.indexOf(id) == -1)
1795                                 preload_id_batch.push(id);
1796
1797                         //console.log("preload ids batch: " + preload_id_batch.toString());
1798
1799                         window.clearTimeout(preload_timeout_id);
1800                         preload_batch_timeout_id = window.setTimeout('preloadBatchedArticles()', 1000);
1801
1802                 }
1803         } catch (e) {
1804                 exception_error("preloadArticleUnderPointer", e);
1805         }
1806 }
1807
1808 function postMouseIn(id) {
1809         try {
1810                 if (post_under_pointer != id) {
1811                         post_under_pointer = id;
1812                         if (!isCdmMode()) {
1813                                 window.setTimeout("preloadArticleUnderPointer(" + id + ")", 250);
1814                         }
1815                 }
1816
1817         } catch (e) {
1818                 exception_error("postMouseIn", e);
1819         }
1820 }
1821
1822 function postMouseOut(id) {
1823         try {
1824                 post_under_pointer = false;
1825         } catch (e) {
1826                 exception_error("postMouseOut", e);
1827         }
1828 }
1829
1830 function headlines_scroll_handler() {
1831         try {
1832
1833                 var e = $("headlinesInnerContainer");
1834
1835                 var toolbar_form = document.forms["main_toolbar_form"];
1836
1837 //              console.log((e.scrollTop + e.offsetHeight) + " vs " + e.scrollHeight + " dis? " +
1838 //                      _infscroll_disable);
1839
1840                 if (e.scrollTop + e.offsetHeight > e.scrollHeight - 100) {
1841                         if (!_infscroll_disable) {
1842                                 viewNextFeedPage();
1843                         }
1844                 }
1845
1846         } catch (e) {
1847                 exception_error("headlines_scroll_handler", e);
1848         }
1849 }
1850
1851 function catchupRelativeToArticle(below) {
1852
1853         try {
1854
1855
1856                 if (!getActiveArticleId()) {
1857                         alert(__("No article is selected."));
1858                         return;
1859                 }
1860
1861                 var visible_ids = getVisibleArticleIds();
1862
1863                 var ids_to_mark = new Array();
1864
1865                 if (!below) {
1866                         for (var i = 0; i < visible_ids.length; i++) {
1867                                 if (visible_ids[i] != getActiveArticleId()) {
1868                                         var e = $("RROW-" + visible_ids[i]);
1869
1870                                         if (e && e.className.match("Unread")) {
1871                                                 ids_to_mark.push(visible_ids[i]);
1872                                         }
1873                                 } else {
1874                                         break;
1875                                 }
1876                         }
1877                 } else {
1878                         for (var i = visible_ids.length-1; i >= 0; i--) {
1879                                 if (visible_ids[i] != getActiveArticleId()) {
1880                                         var e = $("RROW-" + visible_ids[i]);
1881
1882                                         if (e && e.className.match("Unread")) {
1883                                                 ids_to_mark.push(visible_ids[i]);
1884                                         }
1885                                 } else {
1886                                         break;
1887                                 }
1888                         }
1889                 }
1890
1891                 if (ids_to_mark.length == 0) {
1892                         alert(__("No articles found to mark"));
1893                 } else {
1894                         var msg = __("Mark %d article(s) as read?").replace("%d", ids_to_mark.length);
1895
1896                         if (getInitParam("confirm_feed_catchup") != 1 || confirm(msg)) {
1897
1898                                 for (var i = 0; i < ids_to_mark.length; i++) {
1899                                         var e = $("RROW-" + ids_to_mark[i]);
1900                                         e.className = e.className.replace("Unread", "");
1901                                 }
1902
1903                                 var query = "?op=rpc&subop=catchupSelected" +
1904                                         "&cmode=0" + "&ids=" + param_escape(ids_to_mark.toString()); 
1905
1906                                 new Ajax.Request("backend.php", {
1907                                         parameters: query,
1908                                         onComplete: function(transport) { 
1909                                                 catchup_callback2(transport); 
1910                                         } });
1911
1912                         }
1913                 }
1914
1915         } catch (e) {
1916                 exception_error("catchupRelativeToArticle", e);
1917         }
1918 }
1919
1920 function cdmExpandArticle(id) {
1921         try {
1922
1923                 hideAuxDlg();
1924
1925                 var elem = $("CICD-" + active_post_id);
1926
1927                 var upd_img_pic = $("FUPDPIC-" + id);
1928
1929                 if (upd_img_pic && (upd_img_pic.src.match("updated.png") || 
1930                                 upd_img_pic.src.match("fresh_sign.png"))) {
1931
1932                         upd_img_pic.src = "images/blank_icon.gif";
1933                 }
1934
1935                 if (id == active_post_id && Element.visible(elem))
1936                         return true;
1937
1938                 selectArticles("none");
1939
1940                 var old_offset = $("RROW-" + id).offsetTop;
1941
1942                 if (active_post_id && elem && !getInitParam("cdm_expanded")) {
1943                         Element.hide(elem);
1944                         Element.show("CEXC-" + active_post_id);
1945                 }
1946
1947                 active_post_id = id;
1948
1949                 elem = $("CICD-" + id);
1950
1951                 if (!Element.visible(elem)) {
1952                         Element.show(elem);
1953                         Element.hide("CEXC-" + id);
1954
1955                         if ($("CWRAP-" + id).innerHTML == "") {
1956
1957                                 $("FUPDPIC-" + id).src = "images/indicator_tiny.gif";
1958
1959                                 $("CWRAP-" + id).innerHTML = "<div class=\"insensitive\">" + 
1960                                         __("Loading, please wait...") + "</div>";
1961         
1962                                 var query = "?op=rpc&subop=cdmGetArticle&id=" + param_escape(id);
1963         
1964                                 //console.log(query);
1965         
1966                                 new Ajax.Request("backend.php", {
1967                                         parameters: query,
1968                                         onComplete: function(transport) { 
1969                                                 $("FUPDPIC-" + id).src = 'images/blank_icon.gif';
1970         
1971                                                 if (transport.responseXML) {
1972                                                         var article = transport.responseXML.getElementsByTagName("article")[0];
1973                                                         var recv_id = article.getAttribute("id");
1974         
1975                                                         if (recv_id == id)
1976                                                                 $("CWRAP-" + id).innerHTML = article.firstChild.nodeValue;
1977         
1978                                                 } else {
1979                                                         $("CWRAP-" + id).innerHTML = __("Unable to load article.");
1980         
1981                                                 }
1982                                 }});
1983         
1984                         }
1985                 }
1986
1987                 var new_offset = $("RROW-" + id).offsetTop;
1988
1989                 $("headlinesInnerContainer").scrollTop += (new_offset-old_offset);
1990
1991                 if ($("RROW-" + id).offsetTop != old_offset) 
1992                         $("headlinesInnerContainer").scrollTop = new_offset;
1993
1994                 toggleUnread(id, 0, true);
1995                 toggleSelected(id);
1996
1997         } catch (e) {
1998                 exception_error("cdmExpandArticle", e);
1999         }
2000
2001         return false;
2002 }
2003
2004 function fixHeadlinesOrder(ids) {
2005         try {
2006                 for (var i = 0; i < ids.length; i++) {
2007                         var e = $("RROW-" + ids[i]);
2008
2009                         if (e) {
2010                                 if (i % 2 == 0) {
2011                                         e.className = e.className.replace("even", "odd");
2012                                 } else {
2013                                         e.className = e.className.replace("odd", "even");
2014                                 }
2015                         }
2016                 }
2017         } catch (e) {
2018                 exception_error("fixHeadlinesOrder", e);
2019         }
2020 }
2021
2022 function getArticleUnderPointer() {
2023         return post_under_pointer;
2024 }
2025
2026 function zoomToArticle(id) {
2027         try {
2028                 var w = window.open("backend.php?op=view&mode=zoom&id=" + param_escape(id), 
2029                         "ttrss_zoom_" + id,
2030                         "status=0,toolbar=0,location=0,width=450,height=300,scrollbars=1,menubar=0");
2031
2032         } catch (e) {
2033                 exception_error("zoomToArticle", e);
2034         }
2035 }
2036
2037 function scrollArticle(offset) {
2038         try {
2039                 if (!isCdmMode()) {
2040                         var ci = $("content-insert");
2041                         if (ci) {
2042                                 ci.scrollTop += offset;
2043                         }
2044                 } else {
2045                         var hi = $("headlinesInnerContainer");
2046                         if (hi) {
2047                                 hi.scrollTop += offset;
2048                         }
2049
2050                 }
2051         } catch (e) {
2052                 exception_error("scrollArticle", e);
2053         }
2054 }
2055
2056 function show_labels_in_headlines(transport) {
2057         try {
2058                 if (transport.responseXML) {
2059                         var info = transport.responseXML.getElementsByTagName("info-for-headlines")[0];
2060
2061                         var elems = info.getElementsByTagName("entry");
2062
2063                         for (var l = 0; l < elems.length; l++) {
2064                                 var e_id = elems[l].getAttribute("id");
2065
2066                                 if (e_id) {
2067
2068                                         var ctr = $("HLLCTR-" + e_id);
2069
2070                                         if (ctr) {
2071                                                 ctr.innerHTML = elems[l].firstChild.nodeValue;
2072                                         }
2073                                 }
2074
2075                         }
2076
2077                 }
2078         } catch (e) {
2079                 exception_error("show_labels_in_headlines", e);
2080
2081         }
2082 }
2083
2084 function toggleHeadlineActions() {
2085         try {
2086                 var e = $("headlineActionsBody");
2087                 var p = $("headlineActionsDrop");
2088
2089                 if (!Element.visible(e)) {
2090                         Element.show(e);
2091                 } else {
2092                         Element.hide(e);
2093                 }
2094
2095                 e.scrollTop = 0;
2096                 e.style.left = (p.offsetLeft + 1) + "px";
2097                 e.style.top = (p.offsetTop + p.offsetHeight + 2) + "px";
2098
2099         } catch (e) {
2100                 exception_error("toggleHeadlineActions", e);
2101         }
2102 }
2103
2104 function publishWithNote(id, def_note) {
2105         try {
2106                 if (!def_note) def_note = '';
2107
2108                 var note = prompt(__("Please enter a note for this article:"), def_note);
2109
2110                 if (note != undefined) {
2111                         togglePub(id, false, false, note);
2112                 }
2113
2114         } catch (e) {
2115                 exception_error("publishWithNote", e);
2116         }
2117 }
2118
2119 function emailArticle(id) {
2120         try {
2121                 if (!id) {
2122                         var ids = getSelectedArticleIds2();
2123
2124                         if (ids.length == 0) {
2125                                 alert(__("No articles are selected."));
2126                                 return;
2127                         }
2128
2129                         id = ids.toString();
2130                 }
2131
2132                 displayDlg('emailArticle', id, 
2133                    function () {                                
2134                                 document.forms['article_email_form'].destination.focus();
2135
2136                            new Ajax.Autocompleter('destination', 'destination_choices',
2137                                    "backend.php?op=rpc&subop=completeEmails",
2138                                    { tokens: '', paramName: "search" });
2139
2140                         });
2141
2142         } catch (e) {
2143                 exception_error("emailArticle", e);
2144         }
2145 }
2146
2147 function emailArticleDo() {
2148         try {
2149                 var f = document.forms['article_email_form'];
2150
2151                 if (f.destination.value == "") {
2152                         alert("Please fill in the destination email.");
2153                         return;
2154                 }
2155
2156                 if (f.subject.value == "") {
2157                         alert("Please fill in the subject.");
2158                         return;
2159                 }
2160
2161                 var query = Form.serialize("article_email_form");
2162
2163 //              console.log(query);
2164
2165                 new Ajax.Request("backend.php", {
2166                         parameters: query,
2167                         onComplete: function(transport) { 
2168                                 try {
2169
2170                                         var error = transport.responseXML.getElementsByTagName('error')[0];
2171
2172                                         if (error) {
2173                                                 alert(__('Error sending email:') + ' ' + error.firstChild.nodeValue);
2174                                         } else {
2175                                                 notify_info('Your message has been sent.');
2176                                                 closeInfoBox();
2177                                         }
2178
2179                                 } catch (e) {
2180                                         exception_error("sendEmailDo", e);
2181                                 }
2182
2183                         } });
2184
2185         } catch (e) {
2186                 exception_error("emailArticleDo", e);
2187         }
2188 }
2189
2190 function dismissArticle(id) {
2191         try {
2192                 var elem = $("RROW-" + id);
2193
2194                 toggleUnread(id, 0, true);
2195
2196                 new Effect.Fade(elem, {duration : 0.5});
2197
2198                 active_post_id = false;
2199
2200         } catch (e) {
2201                 exception_error("dismissArticle", e);
2202         }
2203 }
2204
2205 function dismissSelectedArticles() {
2206         try {
2207
2208                 var ids = getVisibleArticleIds();
2209                 var tmp = [];
2210                 var sel = [];
2211
2212                 for (var i = 0; i < ids.length; i++) {
2213                         var elem = $("RROW-" + ids[i]);
2214
2215                         if (elem.className && elem.className.match("Selected") && 
2216                                         ids[i] != active_post_id) {
2217                                 new Effect.Fade(elem, {duration : 0.5});
2218                                 sel.push(ids[i]);
2219                         } else {
2220                                 tmp.push(ids[i]);
2221                         }
2222                 }
2223
2224                 if (sel.length > 0)
2225                         selectionToggleUnread(false);
2226
2227                 fixHeadlinesOrder(tmp);
2228
2229         } catch (e) {
2230                 exception_error("dismissSelectedArticles", e);
2231         }
2232 }
2233
2234 function dismissReadArticles() {
2235         try {
2236
2237                 var ids = getVisibleArticleIds();
2238                 var tmp = [];
2239
2240                 for (var i = 0; i < ids.length; i++) {
2241                         var elem = $("RROW-" + ids[i]);
2242
2243                         if (elem.className && !elem.className.match("Unread") && 
2244                                         !elem.className.match("Selected")) {
2245                         
2246                                 new Effect.Fade(elem, {duration : 0.5});
2247                         } else {
2248                                 tmp.push(ids[i]);
2249                         }
2250                 }
2251
2252                 fixHeadlinesOrder(tmp);
2253
2254         } catch (e) {
2255                 exception_error("dismissSelectedArticles", e);
2256         }
2257 }
2258
2259 function getVisibleArticleIds() {
2260         var ids = [];
2261
2262         try {
2263                 var tmp = getLoadedArticleIds();
2264
2265                 for (var i = 0; i < tmp.length; i++) {
2266                         var elem = $("RROW-" + tmp[i]);
2267                         if (elem && Element.visible(elem))
2268                                 ids.push(tmp[i]);
2269                 }
2270
2271         } catch (e) {
2272                 exception_error("getVisibleArticleIds", e);
2273         }
2274
2275         return ids;
2276 }
2277
2278 function cdmClicked(event, id) {
2279         try {
2280                 var shift_key = event.shiftKey;
2281
2282                 hideAuxDlg();
2283
2284                 if (!event.ctrlKey) {
2285                         selectArticles("none");
2286                         toggleSelected(id);
2287
2288                         var elem = $("RROW-" + id);
2289
2290                         if (elem)
2291                                 elem.className = elem.className.replace("Unread", "");
2292
2293                         var upd_img_pic = $("FUPDPIC-" + id);
2294
2295                         if (upd_img_pic && (upd_img_pic.src.match("updated.png") || 
2296                                         upd_img_pic.src.match("fresh_sign.png"))) {
2297
2298                                 upd_img_pic.src = "images/blank_icon.gif";
2299                         }
2300
2301                         var query = "?op=rpc&subop=catchupSelected" +
2302                                 "&cmode=0&ids=" + param_escape(id);
2303
2304                         new Ajax.Request("backend.php", {
2305                                 parameters: query,
2306                                 onComplete: function(transport) { 
2307                                         handle_rpc_reply(transport); 
2308                                 } });
2309
2310                         return true;
2311                 } else {
2312                         toggleSelected(id);
2313                 }
2314
2315         } catch (e) {
2316                 exception_error("cdmClicked");
2317         }
2318
2319         return false;
2320 }
2321
2322 function hlClicked(event, id) {
2323         try {
2324
2325                 if (!event.ctrlKey) {
2326                         view(id);
2327                         return true;
2328                 } else {
2329                         toggleSelected(id);
2330                         return false;
2331                 }
2332
2333         } catch (e) {
2334                 exception_error("hlClicked");
2335         }
2336
2337         return false;
2338 }
2339
2340 function getFirstVisibleHeadlineId() {
2341         var rows = getVisibleArticleIds();
2342         return rows[0];
2343         
2344 }
2345
2346 function getLastVisibleHeadlineId() {
2347         var rows = getVisibleArticleIds();
2348         return rows[rows.length-1];
2349 }
2350
2351 function openArticleInNewWindow(id) {
2352         try {
2353                 console.log("openArticleInNewWindow: " + id);
2354
2355                 var query = "?op=rpc&subop=getArticleLink&id=" + id;
2356                 var wname = "ttrss_article_" + id;
2357
2358                 console.log(query + " " + wname);
2359
2360                 var w = window.open("", wname);
2361
2362                 if (!w) notify_error("Failed to open window for the article");
2363
2364                 new Ajax.Request("backend.php", {
2365                         parameters: query,
2366                         onComplete: function(transport) { 
2367
2368                                         var link = transport.responseXML.getElementsByTagName("link")[0];
2369                                         var id = transport.responseXML.getElementsByTagName("id")[0];
2370                 
2371                                         console.log("open_article received link: " + link);
2372                 
2373                                         if (link && id) {
2374                 
2375                                                 var wname = "ttrss_article_" + id.firstChild.nodeValue;
2376                 
2377                                                 console.log("link url: " + link.firstChild.nodeValue + ", wname " + wname);
2378                 
2379                                                 var w = window.open(link.firstChild.nodeValue, wname);
2380                 
2381                                                 if (!w) { notify_error("Failed to load article in new window"); }
2382                 
2383                                                 if (id) {
2384                                                         id = id.firstChild.nodeValue;
2385                                                         window.setTimeout("toggleUnread(" + id + ", 0)", 100);
2386                                                 }
2387                                         } else {
2388                                                 notify_error("Can't open article: received invalid article link");
2389                                         }
2390                                 } });
2391
2392         } catch (e) {
2393                 exception_error("openArticleInNewWindow", e);
2394         }
2395 }
2396
2397 function isCdmMode() {
2398         return getInitParam("combined_display_mode");
2399 }
2400
2401 function markHeadline(id) {
2402         var row = $("RROW-" + id);
2403         if (row) {
2404                 var is_active = false;
2405         
2406                 if (row.className.match("Active")) {
2407                         is_active = true;
2408                 }
2409                 row.className = row.className.replace("Selected", "");
2410                 row.className = row.className.replace("Active", "");
2411                 row.className = row.className.replace("Insensitive", "");
2412                 
2413                 if (is_active) {
2414                         row.className = row.className = "Active";
2415                 }
2416                 
2417                 var check = $("RCHK-" + id);
2418
2419                 if (check) {
2420                         check.checked = true;
2421                 }
2422
2423                 row.className = row.className + "Selected"; 
2424                 
2425         }
2426 }
2427
2428 function getRelativePostIds(id, limit) {
2429
2430         var tmp = [];
2431
2432         try {
2433
2434                 if (!limit) limit = 3;
2435         
2436                 var ids = getVisibleArticleIds();
2437         
2438                 for (var i = 0; i < ids.length; i++) {
2439                         if (ids[i] == id) {
2440                                 for (var k = 1; k <= limit; k++) {
2441                                         if (i > k-1) tmp.push(ids[i-k]);
2442                                         if (i < ids.length-k) tmp.push(ids[i+k]);
2443                                 }
2444                                 break;
2445                         }
2446                 }
2447
2448         } catch (e) {
2449                 exception_error("getRelativePostIds", e);
2450         }
2451
2452         return tmp;
2453 }
2454
2455 function correctHeadlinesOffset(id) {
2456         
2457         try {
2458
2459                 var container = $("headlinesInnerContainer");
2460                 var row = $("RROW-" + id);
2461         
2462                 var viewport = container.offsetHeight;
2463         
2464                 var rel_offset_top = row.offsetTop - container.scrollTop;
2465                 var rel_offset_bottom = row.offsetTop + row.offsetHeight - container.scrollTop;
2466         
2467                 //console.log("Rtop: " + rel_offset_top + " Rbtm: " + rel_offset_bottom);
2468                 //console.log("Vport: " + viewport);
2469
2470                 if (rel_offset_top <= 0 || rel_offset_top > viewport) {
2471                         container.scrollTop = row.offsetTop;
2472                 } else if (rel_offset_bottom > viewport) {
2473
2474                         /* doesn't properly work with Opera in some cases because
2475                                 Opera fucks up element scrolling */
2476
2477                         container.scrollTop = row.offsetTop + row.offsetHeight - viewport;              
2478                 } 
2479
2480         } catch (e) {
2481                 exception_error("correctHeadlinesOffset", e);
2482         }
2483
2484 }
2485
2486