]> git.wh0rd.org Git - tt-rss.git/blob - functions.js
js: code cleanup
[tt-rss.git] / functions.js
1 var hotkeys_enabled = true;
2 var notify_silent = false;
3 var last_progress_point = 0;
4 var async_counters_work = false;
5 var sanity_check_done = false;
6
7 /* add method to remove element from array */
8
9 Array.prototype.remove = function(s) {
10         for (var i=0; i < this.length; i++) {
11                 if (s == this[i]) this.splice(i, 1);
12         }
13 }
14
15 /* create console.log if it doesn't exist */
16
17 if (!window.console) console = {};
18 console.log = console.log || function(msg) { debug(msg); };
19 console.warn = console.warn || function(msg) { debug(msg); };
20 console.error = console.error || function(msg) { debug(msg); };
21
22 function exception_error(location, e, ext_info) {
23         var msg = format_exception_error(location, e);
24
25         if (!ext_info) ext_info = false;
26
27         disableHotkeys();
28
29         try {
30
31                 var ebc = $("xebContent");
32         
33                 if (ebc) {
34         
35                         Element.show("dialog_overlay");
36                         Element.show("errorBoxShadow");
37         
38                         if (ext_info) {
39                                 if (ext_info.responseText) {
40                                         ext_info = ext_info.responseText;
41                                 }
42                         }
43         
44                         ebc.innerHTML = 
45                                 "<div><b>Error message:</b></div>" +
46                                 "<pre>" + msg + "</pre>";
47
48                         if (ext_info) {
49                                 ebc.innerHTML += "<div><b>Additional information:</b></div>" +
50                                 "<textarea readonly=\"1\">" + ext_info + "</textarea>";
51                         }
52
53                         ebc.innerHTML += "<div><b>Stack trace:</b></div>" +
54                                 "<textarea readonly=\"1\">" + e.stack + "</textarea>";
55         
56                 } else {
57                         alert(msg);
58                 }
59
60         } catch (e) {
61                 alert(msg);
62
63         }
64
65 }
66
67 function format_exception_error(location, e) {
68         var msg;
69
70         if (e.fileName) {
71                 var base_fname = e.fileName.substring(e.fileName.lastIndexOf("/") + 1);
72         
73                 msg = "Exception: " + e.name + ", " + e.message + 
74                         "\nFunction: " + location + "()" +
75                         "\nLocation: " + base_fname + ":" + e.lineNumber;
76
77         } else if (e.description) {
78                 msg = "Exception: " + e.description + "\nFunction: " + location + "()";
79         } else {
80                 msg = "Exception: " + e + "\nFunction: " + location + "()";
81         }
82
83         console.error("EXCEPTION: " + msg);
84
85         return msg;
86 }
87
88
89 function disableHotkeys() {
90         hotkeys_enabled = false;
91 }
92
93 function enableHotkeys() {
94         hotkeys_enabled = true;
95 }
96
97 function param_escape(arg) {
98         if (typeof encodeURIComponent != 'undefined')
99                 return encodeURIComponent(arg); 
100         else
101                 return escape(arg);
102 }
103
104 function param_unescape(arg) {
105         if (typeof decodeURIComponent != 'undefined')
106                 return decodeURIComponent(arg); 
107         else
108                 return unescape(arg);
109 }
110
111 function delay(gap) {
112         var then,now; 
113         then=new Date().getTime();
114         now=then;
115         while((now-then)<gap) {
116                 now=new Date().getTime();
117         }
118 }
119
120 var notify_hide_timerid = false;
121
122 function hide_notify() {
123         var n = $("notify");
124         if (n) {
125                 n.style.display = "none";
126         }
127
128
129 function notify_silent_next() {
130         notify_silent = true;
131 }
132
133 function notify_real(msg, no_hide, n_type) {
134
135         if (notify_silent) {
136                 notify_silent = false;
137                 return;
138         }
139
140         var n = $("notify");
141         var nb = $("notify_body");
142
143         if (!n || !nb) return;
144
145         if (notify_hide_timerid) {
146                 window.clearTimeout(notify_hide_timerid);
147         }
148
149         if (msg == "") {
150                 if (n.style.display == "block") {
151                         notify_hide_timerid = window.setTimeout("hide_notify()", 0);
152                 }
153                 return;
154         } else {
155                 n.style.display = "block";
156         }
157
158         /* types:
159
160                 1 - generic
161                 2 - progress
162                 3 - error
163                 4 - info
164
165         */
166
167         if (typeof __ != 'undefined') {
168                 msg = __(msg);
169         }
170
171         if (n_type == 1) {
172                 n.className = "notify";
173         } else if (n_type == 2) {
174                 n.className = "notifyProgress";
175                 msg = "<img src='"+getInitParam("sign_progress")+"'> " + msg;
176         } else if (n_type == 3) {
177                 n.className = "notifyError";
178                 msg = "<img src='"+getInitParam("sign_excl")+"'> " + msg;
179         } else if (n_type == 4) {
180                 n.className = "notifyInfo";
181                 msg = "<img src='"+getInitParam("sign_info")+"'> " + msg;
182         }
183
184 //      msg = "<img src='images/live_com_loading.gif'> " + msg;
185
186         nb.innerHTML = msg;
187
188         if (!no_hide) {
189                 notify_hide_timerid = window.setTimeout("hide_notify()", 3000);
190         }
191 }
192
193 function notify(msg, no_hide) {
194         notify_real(msg, no_hide, 1);
195 }
196
197 function notify_progress(msg, no_hide) {
198         notify_real(msg, no_hide, 2);
199 }
200
201 function notify_error(msg, no_hide) {
202         notify_real(msg, no_hide, 3);
203
204 }
205
206 function notify_info(msg, no_hide) {
207         notify_real(msg, no_hide, 4);
208 }
209
210 function printLockingError() {
211         notify_info("Please wait until operation finishes.");
212 }
213
214 function cleanSelected(element) {
215         var content = $(element);
216
217         for (i = 0; i < content.rows.length; i++) {
218                 content.rows[i].className = content.rows[i].className.replace("Selected", "");
219         }
220 }
221
222 function getVisibleUnreadHeadlines() {
223         var content = $("headlinesList");
224
225         var rows = new Array();
226
227         if (!content) return rows;
228
229         for (i = 0; i < content.rows.length; i++) {
230                 var row_id = content.rows[i].id.replace("RROW-", "");
231                 if (row_id.length > 0 && content.rows[i].className.match("Unread")) {
232                                 rows.push(row_id);      
233                 }
234         }
235         return rows;
236 }
237
238 function getVisibleHeadlineIds() {
239
240         var content = $("headlinesList");
241
242         var rows = new Array();
243
244         if (!content) return rows;
245
246         for (i = 0; i < content.rows.length; i++) {
247                 var row_id = content.rows[i].id.replace("RROW-", "");
248                 if (row_id.length > 0) {
249                                 rows.push(row_id);      
250                 }
251         }
252         return rows;
253 }
254
255 function getFirstVisibleHeadlineId() {
256         if (isCdmMode()) {
257                 var rows = cdmGetVisibleArticles();
258                 return rows[0];
259         } else {
260                 var rows = getVisibleHeadlineIds();
261                 return rows[0];
262         }
263 }
264
265 function getLastVisibleHeadlineId() {
266         if (isCdmMode()) {
267                 var rows = cdmGetVisibleArticles();
268                 return rows[rows.length-1];
269         } else {
270                 var rows = getVisibleHeadlineIds();
271                 return rows[rows.length-1];
272         }
273 }
274
275 function markHeadline(id) {
276         var row = $("RROW-" + id);
277         if (row) {
278                 var is_active = false;
279         
280                 if (row.className.match("Active")) {
281                         is_active = true;
282                 }
283                 row.className = row.className.replace("Selected", "");
284                 row.className = row.className.replace("Active", "");
285                 row.className = row.className.replace("Insensitive", "");
286                 
287                 if (is_active) {
288                         row.className = row.className = "Active";
289                 }
290                 
291                 var check = $("RCHK-" + id);
292
293                 if (check) {
294                         check.checked = true;
295                 }
296
297                 row.className = row.className + "Selected"; 
298                 
299         }
300 }
301
302 function getFeedIds() {
303         var content = $("feedsList");
304
305         var rows = new Array();
306
307         for (i = 0; i < content.rows.length; i++) {
308                 var id = content.rows[i].id.replace("FEEDR-", "");
309                 if (id.length > 0) {
310                         rows.push(id);
311                 }
312         }
313
314         return rows;
315 }
316
317 function setCookie(name, value, lifetime, path, domain, secure) {
318         
319         var d = false;
320         
321         if (lifetime) {
322                 d = new Date();
323                 d.setTime(d.getTime() + (lifetime * 1000));
324         }
325
326         console.log("setCookie: " + name + " => " + value + ": " + d);
327         
328         int_setCookie(name, value, d, path, domain, secure);
329
330 }
331
332 function int_setCookie(name, value, expires, path, domain, secure) {
333         document.cookie= name + "=" + escape(value) +
334                 ((expires) ? "; expires=" + expires.toGMTString() : "") +
335                 ((path) ? "; path=" + path : "") +
336                 ((domain) ? "; domain=" + domain : "") +
337                 ((secure) ? "; secure" : "");
338 }
339
340 function delCookie(name, path, domain) {
341         if (getCookie(name)) {
342                 document.cookie = name + "=" +
343                 ((path) ? ";path=" + path : "") +
344                 ((domain) ? ";domain=" + domain : "" ) +
345                 ";expires=Thu, 01-Jan-1970 00:00:01 GMT";
346         }
347 }
348                 
349
350 function getCookie(name) {
351
352         var dc = document.cookie;
353         var prefix = name + "=";
354         var begin = dc.indexOf("; " + prefix);
355         if (begin == -1) {
356             begin = dc.indexOf(prefix);
357             if (begin != 0) return null;
358         }
359         else {
360             begin += 2;
361         }
362         var end = document.cookie.indexOf(";", begin);
363         if (end == -1) {
364             end = dc.length;
365         }
366         return unescape(dc.substring(begin + prefix.length, end));
367 }
368
369 function gotoPreferences() {
370         document.location.href = "prefs.php";
371 }
372
373 function gotoMain() {
374         document.location.href = "tt-rss.php";
375 }
376
377 function gotoExportOpml() {
378         document.location.href = "opml.php?op=Export";
379 }
380
381 function parse_counters(reply, scheduled_call) {
382         try {
383
384                 var feeds_found = 0;
385
386                 var elems = JSON.parse(reply.firstChild.nodeValue);
387
388                 for (var l = 0; l < elems.length; l++) {
389
390                         var id = elems[l].id
391                         var kind = elems[l].kind;
392                         var ctr = parseInt(elems[l].counter)
393                         var error = elems[l].error;
394                         var has_img = elems[l].has_img;
395                         var updated = elems[l].updated;
396                         var title = elems[l].title;
397                         var xmsg = elems[l].xmsg;
398         
399                         if (id == "global-unread") {
400
401                                 if (ctr > global_unread) {
402                                         offlineDownloadStart(1);
403                                 }
404
405                                 global_unread = ctr;
406                                 updateTitle();
407                                 continue;
408                         }
409
410                         if (id == "subscribed-feeds") {
411                                 feeds_found = ctr;
412                                 continue;
413                         }
414         
415                         if (kind && kind == "cat") {
416                                 var catctr = $("FCATCTR-" + id);
417                                 if (catctr) {
418                                         catctr.innerHTML = "(" + ctr + ")";
419                                         if (ctr > 0) {
420                                                 catctr.className = "catCtrHasUnread";
421                                         } else {
422                                                 catctr.className = "catCtrNoUnread";
423                                         }
424                                 }
425                                 continue;
426                         }
427                 
428                         var feedctr = $("FEEDCTR-" + id);
429                         var feedu = $("FEEDU-" + id);
430                         var feedr = $("FEEDR-" + id);
431                         var feed_img = $("FIMG-" + id);
432                         var feedlink = $("FEEDL-" + id);
433                         var feedupd = $("FLUPD-" + id);
434
435                         if (updated && feedlink) {
436                                 if (error) {
437                                         feedlink.title = "Error: " + error + " (" + updated + ")";
438                                 } else {
439                                         feedlink.title = "Updated: " + updated;
440                                 }
441                         }
442
443                         if (feedupd) {
444                                 if (!updated) updated = "";
445
446                                 if (error) {
447                                         if (xmsg) {
448                                                 feedupd.innerHTML = updated + " " + xmsg + " (Error)";
449                                         } else {
450                                                 feedupd.innerHTML = updated + " (Error)";
451                                         }
452                                 } else {
453                                         if (xmsg) {
454                                                 feedupd.innerHTML = updated + " " + xmsg;
455                                         } else {
456                                                 feedupd.innerHTML = updated;
457                                         }
458                                 }
459                         }
460
461                         if (has_img && feed_img) {
462                                 if (!feed_img.src.match(id + ".ico")) {
463                                         feed_img.src = getInitParam("icons_url") + "/" + id + ".ico";
464                                 }
465                         }
466
467                         if (feedlink && title) {
468                                 feedlink.innerHTML = title;
469                         }
470
471                         if (feedctr && feedu && feedr) {
472
473                                 if (parseInt(ctr) > 0 && 
474                                                 parseInt(feedu.innerHTML) < parseInt(ctr) && 
475                                                 id == getActiveFeedId() && scheduled_call) {
476
477                                         displayNewContentPrompt(id);
478                                 }
479
480                                 var row_needs_hl = (ctr > 0 && ctr > parseInt(feedu.innerHTML));
481
482                                 feedu.innerHTML = ctr;
483
484                                 if (error) {
485                                         feedr.className = feedr.className.replace("feed", "error");
486                                 } else if (id > 0) {
487                                         feedr.className = feedr.className.replace("error", "feed");
488                                 }
489         
490                                 if (ctr > 0) {                                  
491                                         feedctr.className = "feedCtrHasUnread";
492                                         if (!feedr.className.match("Unread")) {
493                                                 var is_selected = feedr.className.match("Selected");
494                 
495                                                 feedr.className = feedr.className.replace("Selected", "");
496                                                 feedr.className = feedr.className.replace("Unread", "");
497                 
498                                                 feedr.className = feedr.className + "Unread";
499                 
500                                                 if (is_selected) {
501                                                         feedr.className = feedr.className + "Selected";
502                                                 }       
503                                                 
504                                         }
505
506                                         if (row_needs_hl && 
507                                                         !getInitParam("theme_options").match('no_highlights')) { 
508                                                 new Effect.Highlight(feedr, {duration: 1, startcolor: "#fff7d5",
509                                                         queue: { position:'end', scope: 'EFQ-' + id, limit: 1 } } );
510
511                                                 cache_invalidate("F:" + id);
512                                         }
513                                 } else {
514                                         feedctr.className = "feedCtrNoUnread";
515                                         feedr.className = feedr.className.replace("Unread", "");
516                                 }                       
517                         }
518                 }
519
520                 hideOrShowFeeds(getInitParam("hide_read_feeds") == 1);
521
522                 var feeds_stored = number_of_feeds;
523
524                 console.log("Feed counters, C: " + feeds_found + ", S:" + feeds_stored);
525
526                 if (feeds_stored != feeds_found) {
527                         number_of_feeds = feeds_found;
528
529                         if (feeds_stored != 0 && feeds_found != 0) {
530                                 console.log("Subscribed feed number changed, refreshing feedlist");
531                                 setTimeout('updateFeedList(false, false)', 50);
532                         }
533                 } else {
534 /*                      var fl = $("feeds-frame").innerHTML;
535                         if (fl) {
536                                 cache_invalidate("FEEDLIST");
537                                 cache_inject("FEEDLIST", fl, getInitParam("num_feeds"));
538                         } */
539                 }
540
541         } catch (e) {
542                 exception_error("parse_counters", e);
543         }
544 }
545
546 function parse_counters_reply(transport, scheduled_call) {
547
548         if (!transport.responseXML) {
549                 notify_error("Backend did not return valid XML", true);
550                 return;
551         }
552
553         var reply = transport.responseXML.firstChild;
554         
555         if (!reply) {
556                 notify_error("Backend did not return expected XML object", true);
557                 updateTitle("");
558                 return;
559         } 
560
561         if (!transport_error_check(transport)) return;
562
563         var counters = reply.getElementsByTagName("counters")[0];
564         
565         if (counters)
566                 parse_counters(counters, scheduled_call);
567
568         var runtime_info = reply.getElementsByTagName("runtime-info")[0];
569
570         if (runtime_info)
571                 parse_runtime_info(runtime_info);
572
573         if (feedsSortByUnread()) {
574                 resort_feedlist();
575         }
576
577         hideOrShowFeeds(getInitParam("hide_read_feeds") == 1);
578
579 }
580
581 function all_counters_callback2(transport, async_call) {
582         try {
583                 if (async_call) async_counters_work = true;
584                 if (offline_mode) return;
585
586                 parse_counters_reply(transport);
587
588         } catch (e) {
589                 exception_error("all_counters_callback2", e, transport);
590         }
591 }
592
593 function get_feed_unread(id) {
594         try {
595                 return parseInt($("FEEDU-" + id).innerHTML);    
596         } catch (e) {
597                 return -1;
598         }
599 }
600
601 function get_cat_unread(id) {
602         try {
603                 var ctr = $("FCATCTR-" + id).innerHTML;
604                 ctr = ctr.replace("(", "");
605                 ctr = ctr.replace(")", "");
606                 return parseInt(ctr);
607         } catch (e) {
608                 return -1;
609         }
610 }
611
612 function get_feed_entry_unread(elem) {
613
614         var id = elem.id.replace("FEEDR-", "");
615
616         if (id <= 0) {
617                 return -1;
618         }
619
620         try {
621                 return parseInt($("FEEDU-" + id).innerHTML);    
622         } catch (e) {
623                 return -1;
624         }
625 }
626
627 function get_feed_entry_name(elem) {
628         var id = elem.id.replace("FEEDR-", "");
629         return getFeedName(id);
630 }
631
632
633 function resort_category(node, cat_mode) {
634
635         try {
636
637                 console.log("resort_category: " + node + " CM=" + cat_mode);
638         
639                 var by_unread = feedsSortByUnread();
640         
641                 var list = node.getElementsByTagName("LI");
642         
643                 for (i = 0; i < list.length; i++) {
644         
645                         for (j = i+1; j < list.length; j++) {                   
646         
647                                 var tmp_val = get_feed_entry_unread(list[i]);
648                                 var cur_val = get_feed_entry_unread(list[j]);
649         
650                                 var tmp_name = get_feed_entry_name(list[i]);
651                                 var cur_name = get_feed_entry_name(list[j]);
652
653                                 var valid_pair = cat_mode || (list[i].id.match(/FEEDR-[0-9]/) &&
654                                                 list[j].id.match(/FEEDR-[0-9]/));
655
656                                 if (valid_pair && ((by_unread && (cur_val > tmp_val)) || (!by_unread && (cur_name < tmp_name)))) {
657                                         tempnode_i = list[i].cloneNode(true);
658                                         tempnode_j = list[j].cloneNode(true);
659                                         node.replaceChild(tempnode_i, list[j]);
660                                         node.replaceChild(tempnode_j, list[i]);
661                                 }
662                         }
663                 }
664
665         } catch (e) {
666                 exception_error("resort_category", e);
667         }
668
669 }
670
671 function resort_feedlist() {
672         console.log("resort_feedlist");
673
674         if ($("FCATLIST--1")) {
675
676                 var lists = document.getElementsByTagName("UL");
677
678                 for (var i = 0; i < lists.length; i++) {
679                         if (lists[i].id && lists[i].id.match("FCATLIST-")) {
680                                 resort_category(lists[i], true);
681                         }
682                 }
683
684         } else {
685                 resort_category($("feedList"), false);
686         }
687 }
688
689 /** * @(#)isNumeric.js * * Copyright (c) 2000 by Sundar Dorai-Raj
690   * * @author Sundar Dorai-Raj
691   * * Email: sdoraira@vt.edu
692   * * This program is free software; you can redistribute it and/or
693   * * modify it under the terms of the GNU General Public License 
694   * * as published by the Free Software Foundation; either version 2 
695   * * of the License, or (at your option) any later version, 
696   * * provided that any use properly credits the author. 
697   * * This program is distributed in the hope that it will be useful,
698   * * but WITHOUT ANY WARRANTY; without even the implied warranty of
699   * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
700   * * GNU General Public License for more details at http://www.gnu.org * * */
701
702   var numbers=".0123456789";
703   function isNumeric(x) {
704     // is x a String or a character?
705     if(x.length>1) {
706       // remove negative sign
707       x=Math.abs(x)+"";
708       for(j=0;j<x.length;j++) {
709         // call isNumeric recursively for each character
710         number=isNumeric(x.substring(j,j+1));
711         if(!number) return number;
712       }
713       return number;
714     }
715     else {
716       // if x is number return true
717       if(numbers.indexOf(x)>=0) return true;
718       return false;
719     }
720   }
721
722
723 function hideOrShowFeeds(hide) {
724
725         try {
726
727         console.log("hideOrShowFeeds: " + hide);
728
729         if ($("FCATLIST--1")) {
730
731                 var lists = document.getElementsByTagName("UL");
732
733                 for (var i = 0; i < lists.length; i++) {
734                         if (lists[i].id && lists[i].id.match("FCATLIST-")) {
735
736                                 var id = lists[i].id.replace("FCATLIST-", "");
737                                 hideOrShowFeedsCategory(id, hide);
738                         }
739                 }
740
741         } else {
742                 hideOrShowFeedsCategory(null, hide);
743         }
744
745         } catch (e) {
746                 exception_error("hideOrShowFeeds", e);
747         }
748 }
749
750 function hideOrShowFeedsCategory(id, hide) {
751
752         try {
753         
754                 var node = null;
755                 var cat_node = null;
756
757                 if (id) {
758                         node = $("FCATLIST-" + id);
759                         cat_node = $("FCAT-" + id);
760                 } else {
761                         node = $("feedList"); // no categories
762                 }
763
764         //      console.log("hideOrShowFeedsCategory: " + node + " (" + hide + ")");
765         
766                 var cat_unread = 0;
767         
768                 if (!node) {
769                         console.log("hideOrShowFeeds: passed node is null, aborting");
770                         return;
771                 }
772         
773         //      console.log("cat: " + node.id);
774         
775                 if (node.hasChildNodes() && node.firstChild.nextSibling != false) {  
776                         for (i = 0; i < node.childNodes.length; i++) {
777                                 if (node.childNodes[i].nodeName != "LI") { continue; }
778         
779                                 if (node.childNodes[i].style != undefined) {
780         
781                                         var has_unread = (node.childNodes[i].className != "feed" &&
782                                                 node.childNodes[i].className != "label" && 
783                                                 !(!getInitParam("hide_read_shows_special") && 
784                                                         node.childNodes[i].className == "virt") && 
785                                                 node.childNodes[i].className != "error" && 
786                                                 node.childNodes[i].className != "tag");
787                 
788         //                              console.log(node.childNodes[i].id + " --> " + has_unread);
789                 
790                                         if (hide && !has_unread) {
791                                                 //node.childNodes[i].style.display = "none";
792                                                 var id = node.childNodes[i].id;
793                                                 Effect.Fade(node.childNodes[i], {duration : 0.3, 
794                                                         queue: { position: 'end', scope: 'FFADE-' + id, limit: 1 }});
795                                         }
796                 
797                                         if (!hide) {
798                                                 node.childNodes[i].style.display = "list-item";
799                                                 //Effect.Appear(node.childNodes[i], {duration : 0.3});
800                                         }
801                 
802                                         if (has_unread) {
803                                                 node.childNodes[i].style.display = "list-item";
804                                                 cat_unread++;
805                                                 //Effect.Appear(node.childNodes[i], {duration : 0.3});
806                                                 //Effect.Highlight(node.childNodes[i]);
807                                         }
808                                 }
809                         }
810                 }       
811         
812         //      console.log("end cat: " + node.id + " unread " + cat_unread);
813
814                 if (cat_node) {
815
816                         if (cat_unread == 0) {
817                                 if (cat_node.style == undefined) {
818                                         console.log("ERROR: supplied cat_node " + cat_node + 
819                                                 " has no styles. WTF?");
820                                         return;
821                                 }
822                                 if (hide) {
823                                         //cat_node.style.display = "none";
824                                         Effect.Fade(cat_node, {duration : 0.3, 
825                                                 queue: { position: 'end', scope: 'CFADE-' + node.id, limit: 1 }});
826                                 } else {
827                                         cat_node.style.display = "list-item";
828                                 }
829                         } else {
830                                 try {
831                                         cat_node.style.display = "list-item";
832                                 } catch (e) {
833                                         console.log(e);
834                                 }
835                         }
836                 }
837
838 //      console.log("unread for category: " + cat_unread);
839
840         } catch (e) {
841                 exception_error("hideOrShowFeedsCategory", e);
842         }
843 }
844
845 function selectTableRow(r, do_select) {
846         r.className = r.className.replace("Selected", "");
847         
848         if (do_select) {
849                 r.className = r.className + "Selected";
850         }
851 }
852
853 function selectTableRowById(elem_id, check_id, do_select) {
854
855         try {
856
857                 var row = $(elem_id);
858
859                 if (row) {
860                         selectTableRow(row, do_select);
861                 }               
862
863                 var check = $(check_id);
864
865                 if (check) {
866                         check.checked = do_select;
867                 }
868         } catch (e) {
869                 exception_error("selectTableRowById", e);
870         }
871 }
872
873 function selectTableRowsByIdPrefix(content_id, prefix, check_prefix, do_select, 
874         classcheck, reset_others) {
875
876         var content = $(content_id);
877
878         if (!content) {
879                 console.log("[selectTableRows] Element " + content_id + " not found.");
880                 return;
881         }
882
883         for (i = 0; i < content.rows.length; i++) {
884                 if (Element.visible(content.rows[i])) {
885                         if (!classcheck || content.rows[i].className.match(classcheck)) {
886                 
887                                 if (content.rows[i].id.match(prefix)) {
888                                         selectTableRow(content.rows[i], do_select);
889                                 
890                                         var row_id = content.rows[i].id.replace(prefix, "");
891                                         var check = $(check_prefix + row_id);
892         
893                                         if (check) {
894                                                 check.checked = do_select;
895                                         }
896                                 } else if (reset_others) {
897                                         selectTableRow(content.rows[i], false);
898         
899                                         var row_id = content.rows[i].id.replace(prefix, "");
900                                         var check = $(check_prefix + row_id);
901         
902                                         if (check) {
903                                                 check.checked = false;
904                                         }
905         
906                                 }
907                         } else if (reset_others) {
908                                 selectTableRow(content.rows[i], false);
909         
910                                 var row_id = content.rows[i].id.replace(prefix, "");
911                                 var check = $(check_prefix + row_id);
912         
913                                 if (check) {
914                                         check.checked = false;
915                                 }
916         
917                         }
918                 }
919         }
920 }
921
922 function getSelectedTableRowIds(content_id, prefix) {
923
924         var content = $(content_id);
925
926         if (!content) {
927                 console.log("[getSelectedTableRowIds] Element " + content_id + " not found.");
928                 return new Array();
929         }
930
931         var sel_rows = new Array();
932
933         for (i = 0; i < content.rows.length; i++) {
934                 if (content.rows[i].id.match(prefix) && 
935                                 content.rows[i].className.match("Selected")) {
936                                 
937                         var row_id = content.rows[i].id.replace(prefix + "-", "");
938                         sel_rows.push(row_id);  
939                 }
940         }
941
942         return sel_rows;
943
944 }
945
946 function toggleSelectRowById(sender, id) {
947         var row = $(id);
948
949         if (sender.checked) {
950                 if (!row.className.match("Selected")) {
951                         row.className = row.className + "Selected";
952                 }
953         } else {
954                 if (row.className.match("Selected")) {
955                         row.className = row.className.replace("Selected", "");
956                 }
957         }
958 }
959
960 function toggleSelectListRow(sender) {
961         var parent_row = sender.parentNode;
962
963         if (sender.checked) {
964                 if (!parent_row.className.match("Selected")) {
965                         parent_row.className = parent_row.className + "Selected";
966                 }
967         } else {
968                 if (parent_row.className.match("Selected")) {
969                         parent_row.className = parent_row.className.replace("Selected", "");
970                 }
971         }
972 }
973
974 function tSR(sender) {
975         return toggleSelectRow(sender);
976 }
977
978 function toggleSelectRow(sender) {
979         var parent_row = sender.parentNode.parentNode;
980
981         if (sender.checked) {
982                 if (!parent_row.className.match("Selected")) {
983                         parent_row.className = parent_row.className + "Selected";
984                 }
985         } else {
986                 if (parent_row.className.match("Selected")) {
987                         parent_row.className = parent_row.className.replace("Selected", "");
988                 }
989         }
990 }
991
992 function getNextUnreadCat(id) {
993         try {
994                 var rows = $("feedList").getElementsByTagName("LI");
995                 var feeds = new Array();
996
997                 var unread_only = true;
998                 var is_cat = true;
999
1000                 for (var i = 0; i < rows.length; i++) {
1001                         if (rows[i].id.match("FCAT-")) {
1002                                 if (rows[i].id == "FCAT-" + id && is_cat || (Element.visible(rows[i]) && Element.visible(rows[i].parentNode))) {
1003
1004                                         var cat_id = parseInt(rows[i].id.replace("FCAT-", ""));
1005
1006                                         if (cat_id >= 0) {
1007                                                 if (!unread_only || get_cat_unread(cat_id) > 0) {
1008                                                         feeds.push(cat_id);
1009                                                 }
1010                                         }
1011                                 }
1012                         }
1013                 }
1014
1015                 var idx = feeds.indexOf(id);
1016                 if (idx != -1 && idx < feeds.length) {
1017                         return feeds[idx+1];                                    
1018                 } else {
1019                         return feeds.shift();
1020                 }
1021
1022         } catch (e) {
1023                 exception_error("getNextUnreadCat", e);
1024         }
1025 }
1026
1027 function getRelativeFeedId2(id, is_cat, direction, unread_only) {       
1028         try {
1029
1030 //              alert(id + " IC: " + is_cat + " D: " + direction + " U: " + unread_only);
1031
1032                 var rows = $("feedList").getElementsByTagName("LI");
1033                 var feeds = new Array();
1034         
1035                 for (var i = 0; i < rows.length; i++) {
1036                         if (rows[i].id.match("FEEDR-")) {
1037         
1038                                 if (rows[i].id == "FEEDR-" + id && !is_cat || (Element.visible(rows[i]) && Element.visible(rows[i].parentNode))) {
1039         
1040                                         if (!unread_only || 
1041                                                         (rows[i].className.match("Unread") || rows[i].id == "FEEDR-" + id)) {
1042                                                 feeds.push(rows[i].id.replace("FEEDR-", ""));
1043                                         }
1044                                 }
1045                         }
1046
1047                         if (rows[i].id.match("FCAT-")) {
1048                                 if (rows[i].id == "FCAT-" + id && is_cat || (Element.visible(rows[i]) && Element.visible(rows[i].parentNode))) {
1049
1050                                         var cat_id = parseInt(rows[i].id.replace("FCAT-", ""));
1051
1052                                         if (cat_id >= 0) {
1053                                                 if (!unread_only || get_cat_unread(cat_id) > 0) {
1054                                                         feeds.push("CAT:"+cat_id);
1055                                                 }
1056                                         }
1057                                 }
1058                         }
1059                 }
1060         
1061 //              alert(feeds.toString());
1062
1063                 if (!id) {
1064                         if (direction == "next") {
1065                                 return feeds.shift();
1066                         } else {
1067                                 return feeds.pop();
1068                         }
1069                 } else {
1070                         if (direction == "next") {
1071                                 if (is_cat) id = "CAT:" + id;
1072                                 var idx = feeds.indexOf(id);
1073                                 if (idx != -1 && idx < feeds.length) {
1074                                         return feeds[idx+1];                                    
1075                                 } else {
1076                                         return getRelativeFeedId2(false, is_cat, direction, unread_only);
1077                                 }
1078                         } else {
1079                                 if (is_cat) id = "CAT:" + id;
1080                                 var idx = feeds.indexOf(id);
1081                                 if (idx > 0) {
1082                                         return feeds[idx-1];
1083                                 } else {
1084                                         return getRelativeFeedId2(false, is_cat, direction, unread_only);
1085                                 }
1086                         }
1087         
1088                 }
1089
1090         } catch (e) {
1091                 exception_error("getRelativeFeedId2", e);
1092         }
1093 }
1094
1095 function checkboxToggleElement(elem, id) {
1096         if (elem.checked) {
1097                 Effect.Appear(id, {duration : 0.5});
1098         } else {
1099                 Effect.Fade(id, {duration : 0.5});
1100         }
1101 }
1102
1103 function dropboxSelect(e, v) {
1104         for (i = 0; i < e.length; i++) {
1105                 if (e[i].value == v) {
1106                         e.selectedIndex = i;
1107                         break;
1108                 }
1109         }
1110 }
1111
1112 // originally stolen from http://www.11tmr.com/11tmr.nsf/d6plinks/MWHE-695L9Z
1113 // bugfixed just a little bit :-)
1114 function getURLParam(strParamName){
1115   var strReturn = "";
1116   var strHref = window.location.href;
1117
1118   if (strHref.indexOf("#") == strHref.length-1) {
1119                 strHref = strHref.substring(0, strHref.length-1);
1120   }
1121
1122   if ( strHref.indexOf("?") > -1 ){
1123     var strQueryString = strHref.substr(strHref.indexOf("?"));
1124     var aQueryString = strQueryString.split("&");
1125     for ( var iParam = 0; iParam < aQueryString.length; iParam++ ){
1126       if (aQueryString[iParam].indexOf(strParamName + "=") > -1 ){
1127         var aParam = aQueryString[iParam].split("=");
1128         strReturn = aParam[1];
1129         break;
1130       }
1131     }
1132   }
1133   return strReturn;
1134
1135
1136 function leading_zero(p) {
1137         var s = String(p);
1138         if (s.length == 1) s = "0" + s;
1139         return s;
1140 }
1141
1142 function make_timestamp() {
1143         var d = new Date();
1144
1145         return leading_zero(d.getHours()) + ":" + leading_zero(d.getMinutes()) +
1146                         ":" + leading_zero(d.getSeconds());
1147 }
1148
1149
1150 function closeErrorBox() {
1151
1152         if (Element.visible("errorBoxShadow")) {
1153                 Element.hide("dialog_overlay");
1154                 Element.hide("errorBoxShadow");
1155
1156                 enableHotkeys();
1157         }
1158
1159         return false;
1160 }
1161
1162 function closeInfoBox(cleanup) {
1163
1164         try {
1165                 enableHotkeys();
1166
1167                 if (Element.visible("infoBoxShadow")) {
1168                         Element.hide("dialog_overlay");
1169                         Element.hide("infoBoxShadow");
1170
1171                         if (cleanup) $("infoBox").innerHTML = "&nbsp;";
1172                 }
1173         } catch (e) {
1174                 exception_error("closeInfoBox", e);
1175         }
1176         
1177         return false;
1178 }
1179
1180
1181 function displayDlg(id, param, callback) {
1182
1183         notify_progress("Loading, please wait...", true);
1184
1185         disableHotkeys();
1186
1187         var query = "?op=dlg&id=" +
1188                 param_escape(id) + "&param=" + param_escape(param);
1189
1190         new Ajax.Request("backend.php", {
1191                 parameters: query,
1192                 onComplete: function (transport) {
1193                         infobox_callback2(transport);
1194                         if (callback) callback(transport);
1195                 } });
1196
1197         return false;
1198 }
1199
1200 function infobox_submit_callback2(transport) {
1201         closeInfoBox();
1202
1203         try {
1204                 // called from prefs, reload tab
1205                 if (typeof active_tab != 'undefined' && active_tab) {
1206                         selectTab(active_tab, false);
1207                 }
1208         } catch (e) { }
1209
1210         if (transport.responseText) {
1211                 notify_info(transport.responseText);
1212         }
1213 }
1214
1215 function infobox_callback2(transport) {
1216         try {
1217
1218                 console.log("infobox_callback2");
1219
1220                 var box = $('infoBox');
1221                 
1222                 if (box) {                      
1223
1224                         if (!getInitParam("infobox_disable_overlay")) {
1225                                 Element.show("dialog_overlay");
1226                         }
1227
1228                         box.innerHTML=transport.responseText;                   
1229                         Element.show("infoBoxShadow");
1230                         //Effect.SlideDown("infoBoxShadow", {duration : 1.0});
1231
1232
1233                 }
1234
1235                 disableHotkeys();
1236
1237                 notify("");
1238         } catch (e) {
1239                 exception_error("infobox_callback2", e);
1240         }
1241 }
1242
1243 function createFilter() {
1244
1245         try {
1246
1247                 var form = document.forms['filter_add_form'];
1248                 var reg_exp = form.reg_exp.value;
1249         
1250                 if (reg_exp == "") {
1251                         alert(__("Can't add filter: nothing to match on."));
1252                         return false;
1253                 }
1254         
1255                 var query = Form.serialize("filter_add_form");
1256         
1257                 // we can be called from some other tab in Prefs                
1258                 if (typeof active_tab != 'undefined' && active_tab) {
1259                         active_tab = "filterConfig";
1260                 }
1261         
1262                 new Ajax.Request("backend.php?" + query, {
1263                         onComplete: function (transport) {
1264                                 infobox_submit_callback2(transport);
1265                         } });
1266                 
1267                 return true;
1268
1269         } catch (e) {
1270                 exception_error("createFilter", e);
1271         }
1272 }
1273
1274 function isValidURL(s) {
1275         return s.match("http://") != null || s.match("https://") != null || s.match("feed://") != null;
1276 }
1277
1278 function subscribeToFeed() {
1279
1280         try {
1281
1282         var form = document.forms['feed_add_form'];
1283         var feed_url = form.feed_url.value;
1284
1285         if (feed_url == "") {
1286                 alert(__("Can't subscribe: no feed URL given."));
1287                 return false;
1288         }
1289
1290         notify_progress(__("Subscribing to feed..."), true);
1291
1292         var query = Form.serialize("feed_add_form");
1293         
1294         console.log("subscribe q: " + query);
1295
1296         Form.disable("feed_add_form");
1297
1298         new Ajax.Request("backend.php", {
1299                 parameters: query,
1300                 onComplete: function(transport) { 
1301                         //dlg_frefresh_callback(transport); 
1302
1303                         notify('');
1304
1305                         var result = transport.responseXML.getElementsByTagName('result')[0];
1306                         var rc = parseInt(result.getAttribute('code'));
1307
1308                         Form.enable("feed_add_form");
1309
1310                         switch (rc) {
1311                         case 1:
1312                                 closeInfoBox();
1313                                 notify_info(__("Subscribed to %s").replace("%s", feed_url));
1314
1315                                 if (inPreferences()) {
1316                                         updateFeedList();
1317                                 } else {
1318                                         setTimeout('updateFeedList(false, false)', 50);
1319                                 }
1320                                 break;
1321                         case 2:
1322                                 alert(__("Can't subscribe to the specified URL."));
1323                                 break;
1324                         case 0:
1325                                 alert(__("You are already subscribed to this feed."));
1326                                 break;
1327                         }
1328
1329                 } });
1330
1331         } catch (e) {
1332                 exception_error("subscribeToFeed", e);
1333         }
1334
1335         return false;
1336 }
1337
1338 function filterCR(e, f)
1339 {
1340      var key;
1341
1342      if(window.event)
1343           key = window.event.keyCode;     //IE
1344      else
1345           key = e.which;     //firefox
1346
1347         if (key == 13) {
1348                 if (typeof f != 'undefined') {
1349                         f();
1350                         return false;
1351                 } else {
1352                         return false;
1353                 }
1354         } else {
1355                 return true;
1356         }
1357 }
1358
1359 var debug_last_class = "even";
1360
1361 function debug(msg) {
1362
1363         if (debug_last_class == "even") {
1364                 debug_last_class = "odd";
1365         } else {
1366                 debug_last_class = "even";
1367         }
1368
1369         var c = $('debug_output');
1370         if (c && Element.visible(c)) {
1371                 while (c.lastChild != 'undefined' && c.childNodes.length > 100) {
1372                         c.removeChild(c.lastChild);
1373                 }
1374         
1375                 var ts = make_timestamp();
1376                 c.innerHTML = "<li class=\"" + debug_last_class + "\"><span class=\"debugTS\">[" + ts + "]</span> " + 
1377                         msg + "</li>" + c.innerHTML;
1378         }
1379 }
1380
1381 function getInitParam(key) {
1382         return init_params[key];
1383 }
1384
1385 function setInitParam(key, value) {
1386         init_params[key] = value;
1387 }
1388
1389 function fatalError(code, msg, ext_info) {
1390         try {   
1391
1392                 if (!ext_info) ext_info = "N/A";
1393
1394                 if (code == 6) {
1395                         window.location.href = "tt-rss.php";                    
1396                 } else if (code == 5) {
1397                         window.location.href = "update.php";
1398                 } else {
1399         
1400                         if (msg == "") msg = "Unknown error";
1401
1402                         var ebc = $("xebContent");
1403         
1404                         if (ebc) {
1405         
1406                                 Element.show("dialog_overlay");
1407                                 Element.show("errorBoxShadow");
1408                                 Element.hide("xebBtn");
1409
1410                                 if (ext_info) {
1411                                         if (ext_info.responseText) {
1412                                                 ext_info = ext_info.responseText;
1413                                         }
1414                                 }
1415         
1416                                 ebc.innerHTML = 
1417                                         "<div><b>Error message:</b></div>" +
1418                                         "<pre>" + msg + "</pre>" +
1419                                         "<div><b>Additional information:</b></div>" +
1420                                         "<textarea readonly=\"1\">" + ext_info + "</textarea>";
1421                         }
1422                 }
1423
1424         } catch (e) {
1425                 exception_error("fatalError", e);
1426         }
1427 }
1428
1429 function getFeedName(id, is_cat) {      
1430         var e;
1431
1432         if (is_cat) {
1433                 e = $("FCATN-" + id);
1434         } else {
1435                 e = $("FEEDN-" + id);
1436         }
1437         if (e) {
1438                 return e.innerHTML.stripTags();
1439         } else {
1440                 return null;
1441         }
1442 }
1443
1444 function filterDlgCheckType(sender) {
1445
1446         try {
1447
1448                 var ftype = sender[sender.selectedIndex].value;
1449
1450                 var form = document.forms["filter_add_form"];
1451         
1452                 if (!form) {
1453                         form = document.forms["filter_edit_form"];
1454                 }
1455
1456                 if (!form) {
1457                         console.log("filterDlgCheckType: can't find form!");
1458                         return;
1459                 }
1460
1461                 // if selected filter type is 5 (Date) enable the modifier dropbox
1462                 if (ftype == 5) {
1463                         Element.show("filter_dlg_date_mod_box");
1464                         Element.show("filter_dlg_date_chk_box");
1465                 } else {
1466                         Element.hide("filter_dlg_date_mod_box");
1467                         Element.hide("filter_dlg_date_chk_box");
1468
1469                 }
1470
1471         } catch (e) {
1472                 exception_error("filterDlgCheckType", e);
1473         }
1474
1475 }
1476
1477 function filterDlgCheckAction(sender) {
1478
1479         try {
1480
1481                 var action = sender[sender.selectedIndex].value;
1482
1483                 var form = document.forms["filter_add_form"];
1484         
1485                 if (!form) {
1486                         form = document.forms["filter_edit_form"];
1487                 }
1488
1489                 if (!form) {
1490                         console.log("filterDlgCheckAction: can't find form!");
1491                         return;
1492                 }
1493
1494                 var action_param = $("filter_dlg_param_box");
1495
1496                 if (!action_param) {
1497                         console.log("filterDlgCheckAction: can't find action param box!");
1498                         return;
1499                 }
1500
1501                 // if selected action supports parameters, enable params field
1502                 if (action == 4 || action == 6 || action == 7) {
1503                         Element.show(action_param);
1504                         if (action != 7) {
1505                                 Element.show(form.action_param);
1506                                 Element.hide(form.action_param_label);
1507                         } else {
1508                                 Element.show(form.action_param_label);
1509                                 Element.hide(form.action_param);
1510                         }
1511                 } else {
1512                         Element.hide(action_param);
1513                 }
1514
1515         } catch (e) {
1516                 exception_error("filterDlgCheckAction", e);
1517         }
1518
1519 }
1520
1521 function filterDlgCheckDate() {
1522         try {
1523                 var form = document.forms["filter_add_form"];
1524         
1525                 if (!form) {
1526                         form = document.forms["filter_edit_form"];
1527                 }
1528
1529                 if (!form) {
1530                         console.log("filterDlgCheckAction: can't find form!");
1531                         return;
1532                 }
1533
1534                 var reg_exp = form.reg_exp.value;
1535
1536                 var query = "?op=rpc&subop=checkDate&date=" + reg_exp;
1537
1538                 new Ajax.Request("backend.php", {
1539                         parameters: query,
1540                         onComplete: function(transport) { 
1541
1542                                 var form = document.forms["filter_add_form"];
1543         
1544                                 if (!form) {
1545                                         form = document.forms["filter_edit_form"];
1546                                 }
1547
1548                                 if (transport.responseXML) {
1549                                         var result = transport.responseXML.getElementsByTagName("result")[0];
1550
1551                                         if (result && result.firstChild) {
1552                                                 if (result.firstChild.nodeValue == "1") {
1553
1554                                                         new Effect.Highlight(form.reg_exp, {startcolor : '#00ff00'});
1555
1556                                                         return;
1557                                                 }
1558                                         }
1559                                 }
1560
1561                                 new Effect.Highlight(form.reg_exp, {startcolor : '#ff0000'});
1562
1563                         } });
1564
1565
1566         } catch (e) {
1567                 exception_error("filterDlgCheckDate", e);
1568         }
1569 }
1570
1571 function explainError(code) {
1572         return displayDlg("explainError", code);
1573 }
1574
1575 // this only searches loaded headlines list, not in CDM
1576 function getRelativePostIds(id, limit) {
1577
1578         if (!limit) limit = 3;
1579
1580         console.log("getRelativePostIds: " + id + " limit=" + limit);
1581
1582         var ids = new Array();
1583         var container = $("headlinesList");
1584
1585         if (container) {
1586                 var rows = container.rows;
1587
1588                 for (var i = 0; i < rows.length; i++) {
1589                         var r_id = rows[i].id.replace("RROW-", "");
1590
1591                         if (r_id == id) {
1592                                 for (var k = 1; k <= limit; k++) {
1593                                         var nid = false;
1594
1595                                         if (i > k-1) var nid = rows[i-k].id.replace("RROW-", "");
1596                                         if (nid) ids.push(nid);
1597
1598                                         if (i < rows.length-k) nid = rows[i+k].id.replace("RROW-", "");
1599                                         if (nid) ids.push(nid);
1600                                 }
1601
1602                                 return ids;
1603                         }
1604                 }
1605         }
1606
1607         return false;
1608 }
1609
1610 function openArticleInNewWindow(id) {
1611         try {
1612                 console.log("openArticleInNewWindow: " + id);
1613
1614                 var query = "?op=rpc&subop=getArticleLink&id=" + id;
1615                 var wname = "ttrss_article_" + id;
1616
1617                 console.log(query + " " + wname);
1618
1619                 var w = window.open("", wname);
1620
1621                 if (!w) notify_error("Failed to open window for the article");
1622
1623                 new Ajax.Request("backend.php", {
1624                         parameters: query,
1625                         onComplete: function(transport) { 
1626
1627                                         var link = transport.responseXML.getElementsByTagName("link")[0];
1628                                         var id = transport.responseXML.getElementsByTagName("id")[0];
1629                 
1630                                         console.log("open_article received link: " + link);
1631                 
1632                                         if (link && id) {
1633                 
1634                                                 var wname = "ttrss_article_" + id.firstChild.nodeValue;
1635                 
1636                                                 console.log("link url: " + link.firstChild.nodeValue + ", wname " + wname);
1637                 
1638                                                 var w = window.open(link.firstChild.nodeValue, wname);
1639                 
1640                                                 if (!w) { notify_error("Failed to load article in new window"); }
1641                 
1642                                                 if (id) {
1643                                                         id = id.firstChild.nodeValue;
1644                                                         if (!$("headlinesList")) {
1645                                                                 window.setTimeout("toggleUnread(" + id + ", 0)", 100);
1646                                                         }
1647                                                 }
1648                                         } else {
1649                                                 notify_error("Can't open article: received invalid article link");
1650                                         }
1651                                 } });
1652
1653         } catch (e) {
1654                 exception_error("openArticleInNewWindow", e);
1655         }
1656 }
1657
1658 /* http://textsnippets.com/posts/show/835 */
1659
1660 Position.GetWindowSize = function(w) {
1661         w = w ? w : window;
1662         var width = w.innerWidth || (w.document.documentElement.clientWidth || w.document.body.clientWidth);
1663         var height = w.innerHeight || (w.document.documentElement.clientHeight || w.document.body.clientHeight);
1664         return [width, height]
1665 }
1666
1667 /* http://textsnippets.com/posts/show/836 */
1668
1669 Position.Center = function(element, parent) {
1670         var w, h, pw, ph;
1671         var d = Element.getDimensions(element);
1672         w = d.width;
1673         h = d.height;
1674         Position.prepare();
1675         if (!parent) {
1676                 var ws = Position.GetWindowSize();
1677                 pw = ws[0];
1678                 ph = ws[1];
1679         } else {
1680                 pw = parent.offsetWidth;
1681                 ph = parent.offsetHeight;
1682         }
1683         element.style.top = (ph/2) - (h/2) -  Position.deltaY + "px";
1684         element.style.left = (pw/2) - (w/2) -  Position.deltaX + "px";
1685 }
1686
1687
1688 function isCdmMode() {
1689         return !$("headlinesList");
1690 }
1691
1692 function getSelectedArticleIds2() {
1693         var rows = new Array();
1694         var cdm_mode = isCdmMode();
1695
1696         if (cdm_mode) {
1697                 rows = cdmGetSelectedArticles();
1698         } else {        
1699                 rows = getSelectedTableRowIds("headlinesList", "RROW", "RCHK");
1700         }
1701
1702         var ids = new Array();
1703
1704         for (var i = 0; i < rows.length; i++) {
1705                 var chk = $("RCHK-" + rows[i]);
1706                 if (chk && chk.checked) {
1707                         ids.push(rows[i]);
1708                 }
1709         }
1710
1711         return ids;
1712 }
1713
1714 function displayHelpInfobox(topic_id) {
1715
1716         var url = "backend.php?op=help&tid=" + param_escape(topic_id);
1717
1718         var w = window.open(url, "ttrss_help", 
1719                 "status=0,toolbar=0,location=0,width=450,height=500,scrollbars=1,menubar=0");
1720
1721 }
1722
1723 function loading_set_progress(p) {
1724         try {
1725                 if (p < last_progress_point || !Element.visible("overlay")) return;
1726
1727                 console.log("loading_set_progress : " + p + " (" + last_progress_point + ")");
1728
1729                 var o = $("l_progress_i");
1730
1731 //              o.style.width = (p * 2) + "px";
1732
1733                 new Effect.Scale(o, p, { 
1734                         scaleY : false,
1735                         scaleFrom : last_progress_point,
1736                         scaleMode: { originalWidth : 200 },
1737                         queue: { position: 'end', scope: 'LSP-Q', limit: 3 } }); 
1738
1739                 last_progress_point = p;
1740
1741         } catch (e) {
1742                 exception_error("loading_set_progress", e);
1743         }
1744 }
1745
1746 function remove_splash() {
1747         if (Element.visible("overlay")) {
1748                 console.log("about to remove splash, OMG!");
1749                 Element.hide("overlay");
1750                 console.log("removed splash!");
1751         }
1752 }
1753
1754 function getSelectedFeedsFromBrowser() {
1755
1756         var list = $("browseFeedList");
1757
1758         var selected = new Array();
1759         
1760         for (i = 0; i < list.childNodes.length; i++) {
1761                 var child = list.childNodes[i];
1762                 if (child.id && child.id.match("FBROW-")) {
1763                         var id = child.id.replace("FBROW-", "");
1764                         
1765                         var cb = $("FBCHK-" + id);
1766
1767                         if (cb.checked) {
1768                                 selected.push(id);
1769                         }
1770                 }
1771         }
1772
1773         return selected;
1774 }
1775
1776 function updateFeedBrowser() {
1777         try {
1778
1779                 var query = Form.serialize("feed_browser");
1780
1781                 Element.show('feed_browser_spinner');
1782
1783                 new Ajax.Request("backend.php", {
1784                         parameters: query,
1785                         onComplete: function(transport) { 
1786                                 notify('');
1787
1788                                 Element.hide('feed_browser_spinner');
1789
1790                                 var c = $("browseFeedList");
1791                                 var r = transport.responseXML.getElementsByTagName("content")[0];
1792                                 var nr = transport.responseXML.getElementsByTagName("num-results")[0];
1793                                 var mode = transport.responseXML.getElementsByTagName("mode")[0];
1794
1795                                 if (c && r) {
1796                                         c.innerHTML = r.firstChild.nodeValue;
1797                                 }
1798
1799                                 if (parseInt(mode.getAttribute("value")) == 2) {
1800                                         Element.show('feed_archive_remove');
1801                                 } else {
1802                                         Element.hide('feed_archive_remove');
1803                                 }
1804         
1805                         } });
1806
1807         } catch (e) {
1808                 exception_error("updateFeedBrowser", e);
1809         }
1810
1811 }
1812
1813 function transport_error_check(transport) {
1814         try {
1815                 if (transport.responseXML) {
1816                         var error = transport.responseXML.getElementsByTagName("error")[0];
1817
1818                         if (error) {
1819                                 var code = error.getAttribute("error-code");
1820                                 var msg = error.getAttribute("error-msg");
1821                                 if (code != 0) {
1822                                         fatalError(code, msg);
1823                                         return false;
1824                                 }
1825                         }
1826                 }
1827         } catch (e) {
1828                 exception_error("check_for_error_xml", e);
1829         }
1830         return true;
1831 }
1832
1833 function strip_tags(s) {
1834         return s.replace(/<\/?[^>]+(>|$)/g, "");
1835 }
1836
1837 function truncate_string(s, length) {
1838         if (!length) length = 30;
1839         var tmp = s.substring(0, length);
1840         if (s.length > length) tmp += "&hellip;";
1841         return tmp;
1842 }
1843
1844 function hotkey_prefix_timeout() {
1845         try {
1846
1847                 var date = new Date();
1848                 var ts = Math.round(date.getTime() / 1000);
1849
1850                 if (hotkey_prefix_pressed && ts - hotkey_prefix_pressed >= 5) {
1851                         console.log("hotkey_prefix seems to be stuck, aborting");
1852                         hotkey_prefix_pressed = false;
1853                         hotkey_prefix = false;
1854                         Element.hide('cmdline');
1855                 }
1856
1857                 setTimeout("hotkey_prefix_timeout()", 1000);
1858
1859         } catch  (e) {
1860                 exception_error("hotkey_prefix_timeout", e);
1861         }
1862 }
1863
1864 function hideAuxDlg() {
1865         try {
1866                 Element.hide('auxDlg');
1867         } catch (e) {
1868                 exception_error("hideAuxDlg", e);
1869         }
1870 }
1871
1872 function displayNewContentPrompt(id) {
1873         try {
1874
1875                 var msg = "<a href='#' onclick='viewfeed("+id+")'>" +
1876                         __("New articles available in this feed (click to show)") + "</a>";
1877
1878                 msg = msg.replace("%s", getFeedName(id));
1879
1880                 $('auxDlg').innerHTML = msg;
1881
1882                 new Effect.Appear('auxDlg', {duration : 0.5});
1883
1884         } catch (e) {
1885                 exception_error("displayNewContentPrompt", e);
1886         }
1887 }
1888
1889 function feedBrowserSubscribe() {
1890         try {
1891
1892                 var selected = getSelectedFeedsFromBrowser();
1893
1894                 var mode = document.forms['feed_browser'].mode;
1895
1896                 mode = mode[mode.selectedIndex].value;
1897
1898                 if (selected.length > 0) {
1899                         closeInfoBox();
1900
1901                         notify_progress("Loading, please wait...", true);
1902
1903                         var query = "?op=rpc&subop=massSubscribe&ids="+
1904                                 param_escape(selected.toString()) + "&mode=" + param_escape(mode);
1905
1906                         new Ajax.Request("backend.php", {
1907                                 parameters: query,
1908                                 onComplete: function(transport) { 
1909
1910                                         var nf = transport.responseXML.getElementsByTagName('num-feeds')[0];
1911                                         var nf_value = nf.getAttribute("value");
1912
1913                                         notify_info(__("Subscribed to %d feed(s).").replace("%d", nf_value));
1914
1915                                         if (inPreferences()) {
1916                                                 updateFeedList();
1917                                         } else {
1918                                                 setTimeout('updateFeedList(false, false)', 50);
1919                                         }
1920                                 } });
1921
1922                 } else {
1923                         alert(__("No feeds are selected."));
1924                 }
1925
1926         } catch (e) {
1927                 exception_error("feedBrowserSubscribe", e);
1928         }
1929 }
1930
1931 function feedArchiveRemove() {
1932         try {
1933
1934                 var selected = getSelectedFeedsFromBrowser();
1935
1936                 if (selected.length > 0) {
1937
1938                         var pr = __("Remove selected feeds from the archive? Feeds with stored articles will not be removed.");
1939
1940                         if (confirm(pr)) {
1941                                 Element.show('feed_browser_spinner');
1942
1943                                 var query = "?op=rpc&subop=remarchived&ids=" + 
1944                                         param_escape(selected.toString());;
1945
1946                                 new Ajax.Request("backend.php", {
1947                                         parameters: query,
1948                                         onComplete: function(transport) { 
1949                                                 updateFeedBrowser();
1950                                         } }); 
1951                         }
1952
1953                 } else {
1954                         alert(__("No feeds are selected."));
1955                 }
1956
1957         } catch (e) {
1958                 exception_error("feedArchiveRemove", e);
1959         }
1960 }
1961
1962 function uploadIconHandler(rc) {
1963         try {
1964                 switch (rc) {
1965                         case 0:
1966                                 notify_info("Upload complete.");
1967                                 if (inPreferences()) {
1968                                         updateFeedList();
1969                                 } else {
1970                                         setTimeout('updateFeedList(false, false)', 50);
1971                                 }
1972                                 break;
1973                         case 1:
1974                                 notify_error("Upload failed: icon is too big.");
1975                                 break;
1976                         case 2:
1977                                 notify_error("Upload failed.");
1978                                 break;
1979                 }
1980
1981         } catch (e) {
1982                 exception_error("uploadIconHandler", e);
1983         }
1984 }
1985
1986 function removeFeedIcon(id) {
1987
1988         try {
1989
1990                 if (confirm(__("Remove stored feed icon?"))) {
1991                         var query = "backend.php?op=pref-feeds&subop=removeicon&feed_id=" + param_escape(id);
1992
1993                         console.log(query);
1994
1995                         notify_progress("Removing feed icon...", true);
1996
1997                         new Ajax.Request("backend.php", {
1998                                 parameters: query,
1999                                 onComplete: function(transport) { 
2000                                         notify_info("Feed icon removed.");
2001                                         if (inPreferences()) {
2002                                                 updateFeedList();
2003                                         } else {
2004                                                 setTimeout('updateFeedList(false, false)', 50);
2005                                         }
2006                                 } }); 
2007                 }
2008
2009                 return false;
2010         } catch (e) {
2011                 exception_error("uploadFeedIcon", e);
2012         }
2013 }
2014
2015 function uploadFeedIcon() {
2016
2017         try {
2018
2019                 var file = $("icon_file");
2020
2021                 if (file.value.length == 0) {
2022                         alert(__("Please select an image file to upload."));
2023                 } else {
2024                         if (confirm(__("Upload new icon for this feed?"))) {
2025                                 notify_progress("Uploading, please wait...", true);
2026                                 return true;
2027                         }
2028                 }
2029
2030                 return false;
2031
2032         } catch (e) {
2033                 exception_error("uploadFeedIcon", e);
2034         }
2035 }
2036
2037 function addLabel() {
2038
2039         try {
2040
2041                 var caption = prompt(__("Please enter label caption:"), "");
2042
2043                 if (caption != undefined) {
2044         
2045                         if (caption == "") {
2046                                 alert(__("Can't create label: missing caption."));
2047                                 return false;
2048                         }
2049
2050                         var query = "?op=pref-labels&subop=add&caption=" + 
2051                                 param_escape(caption);
2052
2053                         notify_progress("Loading, please wait...", true);
2054
2055                         if (inPreferences()) active_tab = "labelConfig";
2056
2057                         new Ajax.Request("backend.php", {
2058                                 parameters: query,
2059                                 onComplete: function(transport) { 
2060                                         if (inPreferences()) {
2061                                                 infobox_submit_callback2(transport);
2062                                         } else {
2063                                                 updateFeedList();
2064                                         }
2065                         } });
2066
2067                 }
2068
2069         } catch (e) {
2070                 exception_error("addLabel", e);
2071         }
2072 }
2073
2074 function quickAddFeed() {
2075         displayDlg('quickAddFeed', '',
2076            function () {$('feed_url').focus();});
2077 }
2078
2079 function quickAddFilter() {
2080         displayDlg('quickAddFilter', '',
2081            function () {document.forms['filter_add_form'].reg_exp.focus();});
2082 }
2083
2084 function unsubscribeFeed(feed_id, title) {
2085
2086         var msg = __("Unsubscribe from %s?").replace("%s", title);
2087
2088         if (title == undefined || confirm(msg)) {
2089                 notify_progress("Removing feed...");
2090
2091                 var query = "?op=pref-feeds&quiet=1&subop=remove&ids=" + feed_id;
2092
2093                 new Ajax.Request("backend.php", {
2094                         parameters: query,
2095                         onComplete: function(transport) {
2096
2097                                         closeInfoBox();
2098
2099                                         if (inPreferences()) {
2100                                                 updateFeedList();                               
2101                                         } else {
2102                                                 dlg_frefresh_callback(transport, feed_id);
2103                                         }
2104
2105                                 } });
2106         }
2107
2108         return false;
2109 }
2110
2111
2112 function backend_sanity_check_callback(transport) {
2113
2114         try {
2115
2116                 if (sanity_check_done) {
2117                         fatalError(11, "Sanity check request received twice. This can indicate "+
2118                       "presence of Firebug or some other disrupting extension. "+
2119                                 "Please disable it and try again.");
2120                         return;
2121                 }
2122
2123                 if (!transport.responseXML) {
2124                         if (!store) {
2125                                 fatalError(3, "Sanity check: Received reply is not XML", 
2126                                         transport.responseText);
2127                                 return;
2128                         } else {
2129                                 init_offline();
2130                                 return;
2131                         }
2132                 }
2133
2134                 if (getURLParam("offline")) {
2135                         return init_offline();
2136                 }
2137
2138                 var reply = transport.responseXML.getElementsByTagName("error")[0];
2139
2140                 if (!reply) {
2141                         fatalError(3, "Sanity check: invalid RPC reply", transport.responseText);
2142                         return;
2143                 }
2144
2145                 var error_code = reply.getAttribute("error-code");
2146         
2147                 if (error_code && error_code != 0) {
2148                         return fatalError(error_code, reply.getAttribute("error-msg"));
2149                 }
2150
2151                 console.log("sanity check ok");
2152
2153                 var params = transport.responseXML.getElementsByTagName("init-params")[0];
2154
2155                 if (params) {
2156                         console.log('reading init-params...');
2157
2158                         params = JSON.parse(params.firstChild.nodeValue);
2159
2160                         if (params) {
2161                                 for (var i = 0; i < params.length; i++) {
2162         
2163                                         var k = params[i].param;
2164                                         var v = params[i].value;
2165         
2166                                         if (getURLParam('debug')) console.log(k + " => " + v);
2167                                         init_params[k] = v;                                     
2168         
2169                                         if (db) {
2170                                                 db.execute("DELETE FROM init_params WHERE key = ?", [k]);
2171                                                 db.execute("INSERT INTO init_params (key,value) VALUES (?, ?)",
2172                                                         [k, v]);
2173                                         }
2174                                 }
2175                         }
2176                 }
2177
2178                 sanity_check_done = true;
2179
2180                 init_second_stage();
2181
2182         } catch (e) {
2183                 exception_error("backend_sanity_check_callback", e, transport); 
2184         } 
2185 }
2186