]> git.wh0rd.org Git - tt-rss.git/blob - functions.js
add persistent storage for toolbar view options, bump schema
[tt-rss.git] / functions.js
1 var hotkeys_enabled = true;
2 var debug_mode_enabled = false;
3 var xmlhttp_rpc = Ajax.getTransport();
4
5 function browser_has_opacity() {
6         return navigator.userAgent.match("Gecko") != null || 
7                 navigator.userAgent.match("Opera") != null;
8 }
9
10 function is_opera() {
11         return navigator.userAgent.match("Opera");
12 }
13
14 function is_khtml() {
15         return navigator.userAgent.match("KHTML");
16 }
17
18 function is_safari() {
19         return navigator.userAgent.match("Safari");
20 }
21
22 function exception_error(location, e, silent) {
23         var msg;
24
25         if (e.fileName) {
26                 var base_fname = e.fileName.substring(e.fileName.lastIndexOf("/") + 1);
27         
28                 msg = "Exception: " + e.name + ", " + e.message + 
29                         "\nFunction: " + location + "()" +
30                         "\nLocation: " + base_fname + ":" + e.lineNumber;
31                 
32         } else {
33                 msg = "Exception: " + e + "\nFunction: " + location + "()";
34         }
35
36         debug("<b>EXCEPTION: " + msg + "</b>");
37
38         if (!silent) {
39                 alert(msg);
40         }
41 }
42
43 function disableHotkeys() {
44         hotkeys_enabled = false;
45 }
46
47 function enableHotkeys() {
48         hotkeys_enabled = true;
49 }
50
51 function xmlhttp_ready(obj) {
52         return obj.readyState == 4 || obj.readyState == 0 || !obj.readyState;
53 }
54
55 function logout_callback() {
56         var container = document.getElementById('notify');
57         if (xmlhttp.readyState == 4) {
58                 try {
59                         var date = new Date();
60                         var timestamp = Math.round(date.getTime() / 1000);
61                         window.location.href = "tt-rss.php";
62                 } catch (e) {
63                         exception_error("logout_callback", e);
64                 }
65         }
66 }
67
68 function notify_callback() {
69         var container = document.getElementById('notify');
70         if (xmlhttp.readyState == 4) {
71                 container.innerHTML=xmlhttp.responseText;
72         }
73 }
74
75 function rpc_notify_callback() {
76         var container = document.getElementById('notify');
77         if (xmlhttp_rpc.readyState == 4) {
78                 container.innerHTML=xmlhttp_rpc.responseText;
79         }
80 }
81
82 function param_escape(arg) {
83         if (typeof encodeURIComponent != 'undefined')
84                 return encodeURIComponent(arg); 
85         else
86                 return escape(arg);
87 }
88
89 function param_unescape(arg) {
90         if (typeof decodeURIComponent != 'undefined')
91                 return decodeURIComponent(arg); 
92         else
93                 return unescape(arg);
94 }
95
96 function delay(gap) {
97         var then,now; 
98         then=new Date().getTime();
99         now=then;
100         while((now-then)<gap) {
101                 now=new Date().getTime();
102         }
103 }
104
105 var notify_hide_timerid = false;
106
107 function hide_notify() {
108         var n = document.getElementById("notify");
109         if (n) {
110                 n.style.display = "none";
111         }
112
113
114 function notify_real(msg, no_hide, n_type) {
115
116         var n = document.getElementById("notify");
117         var nb = document.getElementById("notify_body");
118
119         if (!n || !nb) return;
120
121         if (notify_hide_timerid) {
122                 window.clearTimeout(notify_hide_timerid);
123         }
124
125         if (msg == "") {
126                 if (n.style.display == "block") {
127                         notify_hide_timerid = window.setTimeout("hide_notify()", 0);
128                 }
129                 return;
130         } else {
131                 n.style.display = "block";
132         }
133
134         /* types:
135
136                 1 - generic
137                 2 - progress
138                 3 - error
139                 4 - info
140
141         */
142
143         if (typeof __ != 'undefined') {
144                 msg = __(msg);
145         }
146
147         if (n_type == 1) {
148                 n.className = "notify";
149         } else if (n_type == 2) {
150                 n.className = "notifyProgress";
151                 msg = "<img src='images/indicator_white.gif'> " + msg;
152         } else if (n_type == 3) {
153                 n.className = "notifyError";
154                 msg = "<img src='images/sign_excl.png'> " + msg;
155         } else if (n_type == 4) {
156                 n.className = "notifyInfo";
157                 msg = "<img src='images/sign_info.png'> " + msg;
158         }
159
160 //      msg = "<img src='images/live_com_loading.gif'> " + msg;
161
162         nb.innerHTML = msg;
163
164         if (!no_hide) {
165                 notify_hide_timerid = window.setTimeout("hide_notify()", 3000);
166         }
167 }
168
169 function notify(msg, no_hide) {
170         notify_real(msg, no_hide, 1);
171 }
172
173 function notify_progress(msg, no_hide) {
174         notify_real(msg, no_hide, 2);
175 }
176
177 function notify_error(msg, no_hide) {
178         notify_real(msg, no_hide, 3);
179
180 }
181
182 function notify_info(msg, no_hide) {
183         notify_real(msg, no_hide, 4);
184 }
185
186 function printLockingError() {
187         notify_info("Please wait until operation finishes.");
188 }
189
190 function hotkey_handler(e) {
191
192         try {
193
194                 var keycode;
195                 var shift_key = false;
196
197                 try {
198                         shift_key = e.shiftKey;
199                 } catch (e) {
200
201                 }
202         
203                 if (!hotkeys_enabled) return;
204         
205                 if (window.event) {
206                         keycode = window.event.keyCode;
207                 } else if (e) {
208                         keycode = e.which;
209                 }
210
211                 if (keycode == 82) { // r
212                         return scheduleFeedUpdate(true);
213                 }
214
215                 if (keycode == 83) { // s
216                         return displayDlg("search", getActiveFeedId());
217                 }
218
219                 if (keycode == 85) { // u
220                         if (getActiveFeedId()) {
221                                 return viewfeed(getActiveFeedId(), "ForceUpdate");
222                         }
223                 }
224         
225                 if (keycode == 65) { // a
226                         return toggleDispRead();
227                 }
228         
229                 var feedlist = document.getElementById('feedList');
230         
231                 if (keycode == 74) { // j
232                         var feed = getActiveFeedId();
233                         var new_feed = getRelativeFeedId(feedlist, feed, 'prev');
234                         if (new_feed) viewfeed(new_feed, '');
235                 }
236         
237                 if (keycode == 75) { // k
238                         var feed = getActiveFeedId();
239                         var new_feed = getRelativeFeedId(feedlist, feed, 'next');
240                         if (new_feed) viewfeed(new_feed, '');
241                 }
242
243                 if (!is_safari() && (keycode == 78 || keycode == 40)) { // n, down
244                         if (typeof moveToPost != 'undefined') {
245                                 return moveToPost('next');
246                         }
247                 }
248         
249                 if (!is_safari() && (keycode == 80 || keycode == 38)) { // p, up
250                         if (typeof moveToPost != 'undefined') {
251                                 return moveToPost('prev');
252                         }
253                 }
254                 
255                 if (keycode == 68 && shift_key) { // d
256                         if (!debug_mode_enabled) {
257                                 document.getElementById('debug_output').style.display = 'block';
258                                 debug('debug mode activated');
259                         } else {
260                                 document.getElementById('debug_output').style.display = 'none';
261                         }
262
263                         debug_mode_enabled = !debug_mode_enabled;
264                 }
265
266                 if (keycode == 190 && shift_key) { // >
267                         viewFeedGoPage(1);
268                 }
269                 
270                 if (keycode == 188 && shift_key) { // <
271                         viewFeedGoPage(-1);
272                 }
273
274                 if (keycode == 191 && shift_key) { // ?
275                         viewFeedGoPage(0);
276                 }
277
278                 if (keycode == 69 && shift_key) { // e
279                         return editFeedDlg(getActiveFeedId());
280                 }
281
282                 if (keycode == 70 && shift_key) { // f
283                         if (getActiveFeedId()) {
284                                 return catchupCurrentFeed();
285                         }
286                 }
287
288                 if (keycode == 80 && shift_key) { // p 
289                         if (getActiveFeedId()) {
290                                 return catchupPage();
291                         }
292                 }
293
294                 if (typeof localHotkeyHandler != 'undefined') {
295                         try {
296                                 return localHotkeyHandler(e);
297                         } catch (e) {
298                                 exception_error("hotkey_handler, local:", e);
299                         }
300                 }
301
302                 debug("KP=" + keycode);
303         } catch (e) {
304                 exception_error("hotkey_handler", e);
305         }
306 }
307
308 function cleanSelectedList(element) {
309         var content = document.getElementById(element);
310
311         if (!document.getElementById("feedCatHolder")) {
312                 for (i = 0; i < content.childNodes.length; i++) {
313                         var child = content.childNodes[i];
314                         try {
315                                 child.className = child.className.replace("Selected", "");
316                         } catch (e) {
317                                 //
318                         }
319                 }
320         } else {
321                 for (i = 0; i < content.childNodes.length; i++) {
322                         var child = content.childNodes[i];
323                         if (child.id == "feedCatHolder") {
324                                 debug(child.id);
325                                 var fcat = child.lastChild;
326                                 for (j = 0; j < fcat.childNodes.length; j++) {
327                                         var feed = fcat.childNodes[j];
328                                         feed.className = feed.className.replace("Selected", "");
329                                 }               
330                         }
331                 } 
332         }
333 }
334
335
336 function cleanSelected(element) {
337         var content = document.getElementById(element);
338
339         for (i = 0; i < content.rows.length; i++) {
340                 content.rows[i].className = content.rows[i].className.replace("Selected", "");
341         }
342 }
343
344 function getVisibleUnreadHeadlines() {
345         var content = document.getElementById("headlinesList");
346
347         var rows = new Array();
348
349         for (i = 0; i < content.rows.length; i++) {
350                 var row_id = content.rows[i].id.replace("RROW-", "");
351                 if (row_id.length > 0 && content.rows[i].className.match("Unread")) {
352                                 rows.push(row_id);      
353                 }
354         }
355         return rows;
356 }
357
358 function getVisibleHeadlineIds() {
359
360         var content = document.getElementById("headlinesList");
361
362         var rows = new Array();
363
364         for (i = 0; i < content.rows.length; i++) {
365                 var row_id = content.rows[i].id.replace("RROW-", "");
366                 if (row_id.length > 0) {
367                                 rows.push(row_id);      
368                 }
369         }
370         return rows;
371 }
372
373 function getFirstVisibleHeadlineId() {
374         var rows = getVisibleHeadlineIds();
375         return rows[0];
376 }
377
378 function getLastVisibleHeadlineId() {
379         var rows = getVisibleHeadlineIds();
380         return rows[rows.length-1];
381 }
382
383 function markHeadline(id) {
384         var row = document.getElementById("RROW-" + id);
385         if (row) {
386                 var is_active = false;
387         
388                 if (row.className.match("Active")) {
389                         is_active = true;
390                 }
391                 row.className = row.className.replace("Selected", "");
392                 row.className = row.className.replace("Active", "");
393                 row.className = row.className.replace("Insensitive", "");
394                 
395                 if (is_active) {
396                         row.className = row.className = "Active";
397                 }
398                 
399                 var check = document.getElementById("RCHK-" + id);
400
401                 if (check) {
402                         check.checked = true;
403                 }
404
405                 row.className = row.className + "Selected"; 
406                 
407         }
408 }
409
410 function getFeedIds() {
411         var content = document.getElementById("feedsList");
412
413         var rows = new Array();
414
415         for (i = 0; i < content.rows.length; i++) {
416                 var id = content.rows[i].id.replace("FEEDR-", "");
417                 if (id.length > 0) {
418                         rows.push(id);
419                 }
420         }
421
422         return rows;
423 }
424
425 function setCookie(name, value, lifetime, path, domain, secure) {
426         
427         var d = false;
428         
429         if (lifetime) {
430                 d = new Date();
431                 d.setTime(d.getTime() + (lifetime * 1000));
432         }
433
434         debug("setCookie: " + name + " => " + value + ": " + d);
435         
436         int_setCookie(name, value, d, path, domain, secure);
437
438 }
439
440 function int_setCookie(name, value, expires, path, domain, secure) {
441         document.cookie= name + "=" + escape(value) +
442                 ((expires) ? "; expires=" + expires.toGMTString() : "") +
443                 ((path) ? "; path=" + path : "") +
444                 ((domain) ? "; domain=" + domain : "") +
445                 ((secure) ? "; secure" : "");
446 }
447
448 function delCookie(name, path, domain) {
449         if (getCookie(name)) {
450                 document.cookie = name + "=" +
451                 ((path) ? ";path=" + path : "") +
452                 ((domain) ? ";domain=" + domain : "" ) +
453                 ";expires=Thu, 01-Jan-1970 00:00:01 GMT";
454         }
455 }
456                 
457
458 function getCookie(name) {
459
460         var dc = document.cookie;
461         var prefix = name + "=";
462         var begin = dc.indexOf("; " + prefix);
463         if (begin == -1) {
464             begin = dc.indexOf(prefix);
465             if (begin != 0) return null;
466         }
467         else {
468             begin += 2;
469         }
470         var end = document.cookie.indexOf(";", begin);
471         if (end == -1) {
472             end = dc.length;
473         }
474         return unescape(dc.substring(begin + prefix.length, end));
475 }
476
477 function disableContainerChildren(id, disable, doc) {
478
479         if (!doc) doc = document;
480
481         var container = doc.getElementById(id);
482
483         if (!container) {
484                 //alert("disableContainerChildren: element " + id + " not found");
485                 return;
486         }
487
488         for (var i = 0; i < container.childNodes.length; i++) {
489                 var child = container.childNodes[i];
490
491                 try {
492                         child.disabled = disable;
493                 } catch (E) {
494
495                 }
496
497                 if (disable) {
498                         if (child.className && child.className.match("button")) {
499                                 child.className = "disabledButton";
500                         }
501                 } else {
502                         if (child.className && child.className.match("disabledButton")) {
503                                 child.className = "button";
504                         }
505                 } 
506         }
507
508 }
509
510 function gotoPreferences() {
511         document.location.href = "prefs.php";
512 }
513
514 function gotoMain() {
515         document.location.href = "tt-rss.php";
516 }
517
518 function gotoExportOpml() {
519         document.location.href = "opml.php?op=Export";
520 }
521
522 function getActiveFeedId() {
523 //      return getCookie("ttrss_vf_actfeed");
524         try {
525                 debug("gAFID: " + active_feed_id);
526                 return active_feed_id;
527         } catch (e) {
528                 exception_error("getActiveFeedId", e);
529         }
530 }
531
532 function activeFeedIsCat() {
533         return active_feed_is_cat;
534 }
535
536 function setActiveFeedId(id) {
537 //      return setCookie("ttrss_vf_actfeed", id);
538         try {
539                 debug("sAFID(" + id + ")");
540                 active_feed_id = id;
541         } catch (e) {
542                 exception_error("setActiveFeedId", e);
543         }
544 }
545
546 function parse_counters(reply, scheduled_call) {
547         try {
548
549                 var feeds_found = 0;
550
551                 if (reply.firstChild && reply.firstChild.firstChild) {
552                         debug("<b>wrong element passed to parse_counters, adjusting.</b>");
553                         reply = reply.firstChild;
554                 }
555
556                 for (var l = 0; l < reply.childNodes.length; l++) {
557                         if (!reply.childNodes[l] ||
558                                 typeof(reply.childNodes[l].getAttribute) == "undefined") {
559                                 // where did this come from?
560                                 continue;
561                         }
562
563                         var id = reply.childNodes[l].getAttribute("id");
564                         var t = reply.childNodes[l].getAttribute("type");
565                         var ctr = reply.childNodes[l].getAttribute("counter");
566                         var error = reply.childNodes[l].getAttribute("error");
567                         var has_img = reply.childNodes[l].getAttribute("hi");
568                         var updated = reply.childNodes[l].getAttribute("updated");
569         
570                         if (id == "global-unread") {
571                                 global_unread = ctr;
572                                 updateTitle();
573                                 continue;
574                         }
575
576                         if (id == "subscribed-feeds") {
577                                 feeds_found = ctr;
578                                 continue;
579                         }
580         
581                         if (t == "category") {
582                                 var catctr = document.getElementById("FCATCTR-" + id);
583                                 if (catctr) {
584                                         catctr.innerHTML = "(" + ctr + ")";
585                                         if (ctr > 0) {
586                                                 catctr.className = "catCtrHasUnread";
587                                         } else {
588                                                 catctr.className = "catCtrNoUnread";
589                                         }
590                                 }
591                                 continue;
592                         }
593                 
594                         var feedctr = document.getElementById("FEEDCTR-" + id);
595                         var feedu = document.getElementById("FEEDU-" + id);
596                         var feedr = document.getElementById("FEEDR-" + id);
597                         var feed_img = document.getElementById("FIMG-" + id);
598                         var feedlink = document.getElementById("FEEDL-" + id);
599                         var feedupd = document.getElementById("FLUPD-" + id);
600
601                         if (updated && feedlink) {
602                                 if (error) {
603                                         feedlink.title = "Error: " + error + " (" + updated + ")";
604                                 } else {
605                                         feedlink.title = "Updated: " + updated;
606                                 }
607                         }
608
609                         if (updated && feedupd) {
610                                 if (error) {
611                                         feedupd.innerHTML = updated + " (Error)";
612                                 } else {
613                                         feedupd.innerHTML = updated;
614                                 }
615                         }
616
617                         if (feedctr && feedu && feedr) {
618
619                                 if (feedu.innerHTML != ctr && id == getActiveFeedId() && scheduled_call) {
620                                         viewCurrentFeed();
621                                 }
622                 
623                                 feedu.innerHTML = ctr;
624
625                                 if (error) {
626                                         feedr.className = feedr.className.replace("feed", "error");
627                                 } else if (id > 0) {
628                                         feedr.className = feedr.className.replace("error", "feed");
629                                 }
630         
631                                 if (ctr > 0) {                                  
632                                         feedctr.className = "odd";
633                                         if (!feedr.className.match("Unread")) {
634                                                 var is_selected = feedr.className.match("Selected");
635                 
636                                                 feedr.className = feedr.className.replace("Selected", "");
637                                                 feedr.className = feedr.className.replace("Unread", "");
638                 
639                                                 feedr.className = feedr.className + "Unread";
640                 
641                                                 if (is_selected) {
642                                                         feedr.className = feedr.className + "Selected";
643                                                 }
644                 
645                                         }
646                                 } else {
647                                         feedctr.className = "invisible";
648                                         feedr.className = feedr.className.replace("Unread", "");
649                                 }                       
650                         }
651                 }
652
653                 hideOrShowFeeds(document, getInitParam("hide_read_feeds") == 1);
654
655                 var feeds_stored = number_of_feeds;
656
657                 debug("Feed counters, C: " + feeds_found + ", S:" + feeds_stored);
658
659                 if (feeds_stored != feeds_found) {
660                         number_of_feeds = feeds_found;
661
662                         if (feeds_stored != 0) {
663                                 debug("Subscribed feed number changed, refreshing feedlist");
664                                 setTimeout('updateFeedList(false, false)', 50);
665                         }
666                 }
667
668         } catch (e) {
669                 exception_error("parse_counters", e);
670         }
671 }
672
673 function parse_counters_reply(xmlhttp, scheduled_call) {
674
675         if (!xmlhttp.responseXML) {
676                 notify_error("Backend did not return valid XML", true);
677                 return;
678         }
679
680         var reply = xmlhttp.responseXML.firstChild;
681         
682         if (!reply) {
683                 notify_error("Backend did not return expected XML object", true);
684                 updateTitle("");
685                 return;
686         } 
687         
688         var error_code = false;
689         var error_msg = false;
690
691         if (reply.firstChild) {
692                 error_code = reply.firstChild.getAttribute("error-code");
693                 error_msg = reply.firstChild.getAttribute("error-msg");
694         }
695
696         if (!error_code) {      
697                 error_code = reply.getAttribute("error-code");
698                 error_msg = reply.getAttribute("error-msg");
699         }
700         
701         if (error_code && error_code != 0) {
702                 debug("refetch_callback: got error code " + error_code);
703                 return fatalError(error_code, error_msg);
704         }
705
706         var counters = reply.firstChild;
707         
708         parse_counters(counters, scheduled_call);
709
710         var runtime_info = counters.nextSibling;
711
712         parse_runtime_info(runtime_info);
713
714         if (getInitParam("feeds_sort_by_unread") == 1) {
715                         resort_feedlist();              
716         }       
717
718         hideOrShowFeeds(document, getInitParam("hide_read_feeds") == 1);
719
720 }
721
722 function all_counters_callback() {
723         if (xmlhttp_rpc.readyState == 4) {
724                 try {
725 /*                      if (!xmlhttp_rpc.responseXML || !xmlhttp_rpc.responseXML.firstChild) {
726                                 debug("[all_counters_callback] backend did not return valid XML");
727                                 return;
728                         }
729
730                         debug("in all_counters_callback : " + xmlhttp_rpc.responseXML);
731
732                         var reply = xmlhttp_rpc.responseXML.firstChild;
733
734                         var counters = reply.firstChild;
735
736                         parse_counters(counters);
737
738                         var runtime = counters.nextSibling;
739
740                         if (runtime) {
741                                 parse_runtime_info(runtime);
742                         }
743
744                         if (getInitParam("feeds_sort_by_unread") == 1) {
745                                 resort_feedlist();              
746                         }       
747
748                         hideOrShowFeeds(document, getInitParam("hide_read_feeds") == 1); */
749                         
750                         debug("in all_counters_callback");
751
752                         parse_counters_reply(xmlhttp_rpc);
753
754                 } catch (e) {
755                         exception_error("all_counters_callback", e);
756                 }
757         }
758 }
759
760 function get_feed_entry_unread(doc, elem) {
761
762         var id = elem.id.replace("FEEDR-", "");
763
764         if (id <= 0) {
765                 return -1;
766         }
767
768         try {
769                 return parseInt(doc.getElementById("FEEDU-" + id).innerHTML);   
770         } catch (e) {
771                 return -1;
772         }
773 }
774
775 function resort_category(doc, node) {
776         debug("resort_category: " + node);
777
778         if (node.hasChildNodes() && node.firstChild.nextSibling != false) {  
779                 for (i = 0; i < node.childNodes.length; i++) {
780                         if (node.childNodes[i].nodeName != "LI") { continue; }
781
782                         if (get_feed_entry_unread(doc, node.childNodes[i]) < 0) {
783                                 continue;
784                         }
785
786                         for (j = i+1; j < node.childNodes.length; j++) {                        
787                                 if (node.childNodes[j].nodeName != "LI") { continue; }  
788
789                                 var tmp_val = get_feed_entry_unread(doc, node.childNodes[i]);
790                                 var cur_val = get_feed_entry_unread(doc, node.childNodes[j]);
791
792                                 if (cur_val > tmp_val) {
793                                         tempnode_i = node.childNodes[i].cloneNode(true);
794                                         tempnode_j = node.childNodes[j].cloneNode(true);
795                                         node.replaceChild(tempnode_i, node.childNodes[j]);
796                                         node.replaceChild(tempnode_j, node.childNodes[i]);
797                                 }
798                         }
799
800                 }
801         }
802
803 }
804
805 function resort_feedlist() {
806         debug("resort_feedlist");
807
808         var fd = document;
809
810         if (fd.getElementById("feedCatHolder")) {
811
812                 var feeds = fd.getElementById("feedList");
813                 var child = feeds.firstChild;
814
815                 while (child) {
816
817                         if (child.id == "feedCatHolder") {
818                                 resort_category(fd, child.firstChild);
819                         }
820         
821                         child = child.nextSibling;
822                 }
823
824         } else {
825                 resort_category(fd, fd.getElementById("feedList"));
826         }
827 }
828
829 function update_all_counters(feed) {
830         if (xmlhttp_ready(xmlhttp_rpc)) {
831                 var query = "backend.php?op=rpc&subop=getAllCounters";
832
833                 if (feed > 0) {
834                         query = query + "&aid=" + feed;
835                 }
836
837                 if (tagsAreDisplayed()) {
838                         query = query + "&omode=lt";
839                 } else {
840                         query = query + "&omode=flc";
841                 }
842
843                 debug("update_all_counters QUERY: " + query);
844
845                 var date = new Date();
846                 var timestamp = Math.round(date.getTime() / 1000);
847                 query = query + "&ts=" + timestamp
848
849                 xmlhttp_rpc.open("GET", query, true);
850                 xmlhttp_rpc.onreadystatechange=all_counters_callback;
851                 xmlhttp_rpc.send(null);
852         }
853 }
854
855 function popupHelp(tid) {
856         var w = window.open("backend.php?op=help&tid=" + tid,
857                 "Popup Help", 
858                 "menubar=no,location=no,resizable=yes,scrollbars=yes,status=no");
859 }
860
861 /** * @(#)isNumeric.js * * Copyright (c) 2000 by Sundar Dorai-Raj
862   * * @author Sundar Dorai-Raj
863   * * Email: sdoraira@vt.edu
864   * * This program is free software; you can redistribute it and/or
865   * * modify it under the terms of the GNU General Public License 
866   * * as published by the Free Software Foundation; either version 2 
867   * * of the License, or (at your option) any later version, 
868   * * provided that any use properly credits the author. 
869   * * This program is distributed in the hope that it will be useful,
870   * * but WITHOUT ANY WARRANTY; without even the implied warranty of
871   * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
872   * * GNU General Public License for more details at http://www.gnu.org * * */
873
874   var numbers=".0123456789";
875   function isNumeric(x) {
876     // is x a String or a character?
877     if(x.length>1) {
878       // remove negative sign
879       x=Math.abs(x)+"";
880       for(j=0;j<x.length;j++) {
881         // call isNumeric recursively for each character
882         number=isNumeric(x.substring(j,j+1));
883         if(!number) return number;
884       }
885       return number;
886     }
887     else {
888       // if x is number return true
889       if(numbers.indexOf(x)>=0) return true;
890       return false;
891     }
892   }
893
894
895 function hideOrShowFeeds(doc, hide) {
896
897         debug("hideOrShowFeeds: " + doc + ", " + hide);
898
899         var fd = document;
900
901         var list = fd.getElementById("feedList");
902
903         if (fd.getElementById("feedCatHolder")) {
904
905                 var feeds = fd.getElementById("feedList");
906                 var child = feeds.firstChild;
907
908                 while (child) {
909
910                         if (child.id == "feedCatHolder") {
911                                 hideOrShowFeedsCategory(fd, child.firstChild, hide, child.previousSibling);
912                         }
913         
914                         child = child.nextSibling;
915                 }
916
917         } else {
918                 hideOrShowFeedsCategory(fd, fd.getElementById("feedList"), hide);
919         }
920 }
921
922 function hideOrShowFeedsCategory(doc, node, hide, cat_node) {
923
924 //      debug("hideOrShowFeedsCategory: " + node + " (" + hide + ")");
925
926         var cat_unread = 0;
927
928         if (!node) {
929                 debug("hideOrShowFeeds: passed node is null, aborting");
930                 return;
931         }
932
933         if (node.hasChildNodes() && node.firstChild.nextSibling != false) {  
934                 for (i = 0; i < node.childNodes.length; i++) {
935                         if (node.childNodes[i].nodeName != "LI") { continue; }
936
937                         if (node.childNodes[i].style != undefined) {
938
939                                 var has_unread = (node.childNodes[i].className != "feed");
940         
941         //                      debug(node.childNodes[i].id + " --> " + has_unread);
942         
943                                 if (hide && !has_unread) {
944                                         node.childNodes[i].style.display = "none";
945                                 }
946         
947                                 if (!hide) {
948                                         node.childNodes[i].style.display = "list-item";
949                                 }
950         
951                                 if (has_unread) {
952                                         node.childNodes[i].style.display = "list-item";
953                                         cat_unread++;
954                                 }
955                         }
956                 }
957         }       
958
959         if (cat_unread == 0) {
960                 if (cat_node.style == undefined) {
961                         debug("ERROR: supplied cat_node " + cat_node + 
962                                 " has no styles. WTF?");
963                         return;
964                 }
965                 if (hide) {
966                         cat_node.style.display = "none";
967                 } else {
968                         cat_node.style.display = "list-item";
969                 }
970         } else {
971                 try {
972                         cat_node.style.display = "list-item";
973                 } catch (e) {
974                         debug(e);
975                 }
976         }
977
978 //      debug("unread for category: " + cat_unread);
979 }
980
981 function selectTableRow(r, do_select) {
982         r.className = r.className.replace("Selected", "");
983         
984         if (do_select) {
985                 r.className = r.className + "Selected";
986         }
987 }
988
989 function selectTableRowById(elem_id, check_id, do_select) {
990
991         try {
992
993                 var row = document.getElementById(elem_id);
994
995                 if (row) {
996                         selectTableRow(row, do_select);
997                 }               
998
999                 var check = document.getElementById(check_id);
1000
1001                 if (check) {
1002                         check.checked = do_select;
1003                 }
1004         } catch (e) {
1005                 exception_error("selectTableRowById", e);
1006         }
1007 }
1008
1009 function selectTableRowsByIdPrefix(content_id, prefix, check_prefix, do_select, 
1010         classcheck, reset_others) {
1011
1012         var content = document.getElementById(content_id);
1013
1014         if (!content) {
1015                 alert("[selectTableRows] Element " + content_id + " not found.");
1016                 return;
1017         }
1018
1019         for (i = 0; i < content.rows.length; i++) {
1020                 if (!classcheck || content.rows[i].className.match(classcheck)) {
1021         
1022                         if (content.rows[i].id.match(prefix)) {
1023                                 selectTableRow(content.rows[i], do_select);
1024                         
1025                                 var row_id = content.rows[i].id.replace(prefix, "");
1026                                 var check = document.getElementById(check_prefix + row_id);
1027
1028                                 if (check) {
1029                                         check.checked = do_select;
1030                                 }
1031                         } else if (reset_others) {
1032                                 selectTableRow(content.rows[i], false);
1033
1034                                 var row_id = content.rows[i].id.replace(prefix, "");
1035                                 var check = document.getElementById(check_prefix + row_id);
1036
1037                                 if (check) {
1038                                         check.checked = false;
1039                                 }
1040
1041                         }
1042                 } else if (reset_others) {
1043                         selectTableRow(content.rows[i], false);
1044
1045                         var row_id = content.rows[i].id.replace(prefix, "");
1046                         var check = document.getElementById(check_prefix + row_id);
1047
1048                         if (check) {
1049                                 check.checked = false;
1050                         }
1051
1052                 }
1053         }
1054 }
1055
1056 function getSelectedTableRowIds(content_id, prefix) {
1057
1058         var content = document.getElementById(content_id);
1059
1060         if (!content) {
1061                 alert("[getSelectedTableRowIds] Element " + content_id + " not found.");
1062                 return;
1063         }
1064
1065         var sel_rows = new Array();
1066
1067         for (i = 0; i < content.rows.length; i++) {
1068                 if (content.rows[i].id.match(prefix) && 
1069                                 content.rows[i].className.match("Selected")) {
1070                                 
1071                         var row_id = content.rows[i].id.replace(prefix + "-", "");
1072                         sel_rows.push(row_id);  
1073                 }
1074         }
1075
1076         return sel_rows;
1077
1078 }
1079
1080 function toggleSelectRowById(sender, id) {
1081         var row = document.getElementById(id);
1082
1083         if (sender.checked) {
1084                 if (!row.className.match("Selected")) {
1085                         row.className = row.className + "Selected";
1086                 }
1087         } else {
1088                 if (row.className.match("Selected")) {
1089                         row.className = row.className.replace("Selected", "");
1090                 }
1091         }
1092 }
1093
1094 function toggleSelectListRow(sender) {
1095         var parent_row = sender.parentNode;
1096
1097         if (sender.checked) {
1098                 if (!parent_row.className.match("Selected")) {
1099                         parent_row.className = parent_row.className + "Selected";
1100                 }
1101         } else {
1102                 if (parent_row.className.match("Selected")) {
1103                         parent_row.className = parent_row.className.replace("Selected", "");
1104                 }
1105         }
1106 }
1107
1108
1109 function toggleSelectRow(sender) {
1110         var parent_row = sender.parentNode.parentNode;
1111
1112         if (sender.checked) {
1113                 if (!parent_row.className.match("Selected")) {
1114                         parent_row.className = parent_row.className + "Selected";
1115                 }
1116         } else {
1117                 if (parent_row.className.match("Selected")) {
1118                         parent_row.className = parent_row.className.replace("Selected", "");
1119                 }
1120         }
1121 }
1122
1123 function openExternalUrl(url) {
1124         var w = window.open(url);
1125 }
1126
1127 function getRelativeFeedId(list, id, direction, unread_only) {  
1128         if (!id) {
1129                 if (direction == "next") {
1130                         for (i = 0; i < list.childNodes.length; i++) {
1131                                 var child = list.childNodes[i];
1132                                 if (child.id && child.id == "feedCatHolder") {
1133                                         if (child.lastChild) {
1134                                                 var cr = getRelativeFeedId(child.firstChild, id, direction, unread_only);
1135                                                 if (cr) return cr;                                      
1136                                         }
1137                                 } else if (child.id && child.id.match("FEEDR-")) {
1138                                         return child.id.replace('FEEDR-', '');
1139                                 }                               
1140                         }
1141                 }
1142
1143                 // FIXME select last feed doesn't work when only unread feeds are visible
1144
1145                 if (direction == "prev") {
1146                         for (i = list.childNodes.length-1; i >= 0; i--) {
1147                                 var child = list.childNodes[i];                         
1148                                 if (child.id == "feedCatHolder") {
1149                                         if (child.firstChild) {
1150                                                 var cr = getRelativeFeedId(child.firstChild, id, direction);
1151                                                 if (cr) return cr;                                      
1152                                         }
1153                                 } else if (child.id.match("FEEDR-")) {
1154                                 
1155                                         if (getInitParam("hide_read_feeds") == 1) {
1156                                                 if (child.className != "feed") {
1157                                                         alert(child.className);
1158                                                         return child.id.replace('FEEDR-', '');                                          
1159                                                 }                                                       
1160                                         } else {
1161                                                         return child.id.replace('FEEDR-', '');                                          
1162                                         }                               
1163                                 }                               
1164                         }
1165                 }
1166         } else {
1167         
1168                 var feed = list.ownerDocument.getElementById("FEEDR-" + id);
1169                 
1170                 if (getInitParam("hide_read_feeds") == 1) {
1171                         unread_only = true;
1172                 }
1173
1174                 if (direction == "next") {
1175
1176                         var e = feed;
1177
1178                         while (e) {
1179
1180                                 if (e.nextSibling) {
1181                                 
1182                                         e = e.nextSibling;
1183                                         
1184                                 } else if (e.parentNode.parentNode.nextSibling) {
1185
1186                                         var this_cat = e.parentNode.parentNode;
1187
1188                                         e = false;
1189
1190                                         if (this_cat && this_cat.nextSibling) {
1191                                                 while (!e && this_cat.nextSibling) {
1192                                                         this_cat = this_cat.nextSibling;
1193                                                         if (this_cat.id == "feedCatHolder") {
1194                                                                 e = this_cat.firstChild.firstChild;
1195                                                         }
1196                                                 }
1197                                         }
1198
1199                                 } else {
1200                                         e = false;
1201                            }
1202
1203                                 if (e) {
1204                                         if (!unread_only || (unread_only && e.className != "feed" &&
1205                                                         e.className.match("feed")))     {
1206                                                 return e.id.replace("FEEDR-", "");
1207                                         }
1208                                 }
1209                         }
1210                         
1211                 } else if (direction == "prev") {
1212
1213                         var e = feed;
1214
1215                         while (e) {
1216
1217                                 if (e.previousSibling) {
1218                                 
1219                                         e = e.previousSibling;
1220                                         
1221                                 } else if (e.parentNode.parentNode.previousSibling) {
1222
1223                                         var this_cat = e.parentNode.parentNode;
1224
1225                                         e = false;
1226
1227                                         if (this_cat && this_cat.previousSibling) {
1228                                                 while (!e && this_cat.previousSibling) {
1229                                                         this_cat = this_cat.previousSibling;
1230                                                         if (this_cat.id == "feedCatHolder") {
1231                                                                 e = this_cat.firstChild.lastChild;
1232                                                         }
1233                                                 }
1234                                         }
1235
1236                                 } else {
1237                                         e = false;
1238                            }
1239
1240                                 if (e) {
1241                                         if (!unread_only || (unread_only && e.className != "feed" && 
1242                                                         e.className.match("feed")))     {
1243                                                 return e.id.replace("FEEDR-", "");
1244                                         }
1245                                 }
1246                         }
1247                 }
1248         }
1249 }
1250
1251 function showBlockElement(id) {
1252         var elem = document.getElementById(id);
1253
1254         if (elem) {
1255                 elem.style.display = "block";
1256         } else {
1257                 alert("[showBlockElement] can't find element with id " + id);
1258         } 
1259 }
1260
1261 function hideParentElement(e) {
1262         e.parentNode.style.display = "none";
1263 }
1264
1265 function dropboxSelect(e, v) {
1266         for (i = 0; i < e.length; i++) {
1267                 if (e[i].value == v) {
1268                         e.selectedIndex = i;
1269                         break;
1270                 }
1271         }
1272 }
1273
1274 // originally stolen from http://www.11tmr.com/11tmr.nsf/d6plinks/MWHE-695L9Z
1275 // bugfixed just a little bit :-)
1276 function getURLParam(strParamName){
1277   var strReturn = "";
1278   var strHref = window.location.href;
1279
1280   if (strHref.indexOf("#") == strHref.length-1) {
1281                 strHref = strHref.substring(0, strHref.length-1);
1282   }
1283
1284   if ( strHref.indexOf("?") > -1 ){
1285     var strQueryString = strHref.substr(strHref.indexOf("?"));
1286     var aQueryString = strQueryString.split("&");
1287     for ( var iParam = 0; iParam < aQueryString.length; iParam++ ){
1288       if (aQueryString[iParam].indexOf(strParamName + "=") > -1 ){
1289         var aParam = aQueryString[iParam].split("=");
1290         strReturn = aParam[1];
1291         break;
1292       }
1293     }
1294   }
1295   return strReturn;
1296
1297
1298 function leading_zero(p) {
1299         var s = String(p);
1300         if (s.length == 1) s = "0" + s;
1301         return s;
1302 }
1303
1304 function closeInfoBox(cleanup) {
1305         var box = document.getElementById('infoBox');
1306         var shadow = document.getElementById('infoBoxShadow');
1307
1308         if (shadow) {
1309                 shadow.style.display = "none";
1310         } else if (box) {
1311                 box.style.display = "none";
1312         }
1313
1314         if (cleanup) box.innerHTML = "&nbsp;";
1315
1316         enableHotkeys();
1317
1318         return false;
1319 }
1320
1321
1322 function displayDlg(id, param) {
1323
1324         if (!xmlhttp_ready(xmlhttp)) {
1325                 printLockingError();
1326                 return
1327         }
1328
1329         notify_progress("Loading, please wait...");
1330
1331         xmlhttp.open("GET", "backend.php?op=dlg&id=" +
1332                 param_escape(id) + "&param=" + param_escape(param), true);
1333         xmlhttp.onreadystatechange=infobox_callback;
1334         xmlhttp.send(null);
1335
1336         disableHotkeys();
1337
1338         return false;
1339 }
1340
1341 function infobox_submit_callback() {
1342         if (xmlhttp.readyState == 4) {
1343                 closeInfoBox();
1344
1345                 try {
1346                         // called from prefs, reload tab
1347                         if (active_tab) {
1348                                 selectTab(active_tab, false);
1349                         }
1350                 } catch (e) { }
1351
1352                 if (xmlhttp.responseText) {
1353                         notify_info(xmlhttp.responseText);
1354                 }
1355
1356         } 
1357 }
1358
1359 function infobox_callback() {
1360         if (xmlhttp.readyState == 4) {
1361                 var box = document.getElementById('infoBox');
1362                 var shadow = document.getElementById('infoBoxShadow');
1363                 if (box) {                      
1364                         box.innerHTML=xmlhttp.responseText;                     
1365                         if (shadow) {
1366                                 shadow.style.display = "block";
1367                         } else {
1368                                 box.style.display = "block";                            
1369                         }
1370                 }
1371                 notify("");
1372         }
1373 }
1374
1375 function addFilter() {
1376
1377         if (!xmlhttp_ready(xmlhttp)) {
1378                 printLockingError();
1379                 return
1380         }
1381
1382         var form = document.forms['filter_add_form'];
1383         var reg_exp = form.reg_exp.value;
1384
1385         if (reg_exp == "") {
1386                 alert("Can't add filter: nothing to match on.");
1387                 return false;
1388         }
1389
1390         var query = Form.serialize("filter_add_form");
1391
1392         xmlhttp.open("GET", "backend.php?" + query, true);
1393         xmlhttp.onreadystatechange=infobox_submit_callback;
1394         xmlhttp.send(null);
1395
1396         return true;
1397 }
1398
1399 function toggleSubmitNotEmpty(e, submit_id) {
1400         try {
1401                 document.getElementById(submit_id).disabled = (e.value == "")
1402         } catch (e) {
1403                 exception_error("toggleSubmitNotEmpty", e);
1404         }
1405 }
1406
1407 function isValidURL(s) {
1408         return s.match("http://") != null || s.match("https://") != null || s.match("feed://") != null;
1409 }
1410
1411 function qaddFeed() {
1412
1413         if (!xmlhttp_ready(xmlhttp)) {
1414                 printLockingError();
1415                 return
1416         }
1417
1418         var form = document.forms['feed_add_form'];
1419         var feed_url = form.feed_url.value;
1420
1421         if (feed_url == "") {
1422                 alert("Can't subscribe: no feed URL given.");
1423                 return false;
1424         }
1425
1426         notify_progress("Adding feed...");
1427
1428         closeInfoBox();
1429
1430         var feeds_doc = document;
1431
1432 //      feeds_doc.location.href = "backend.php?op=error&msg=Loading,%20please wait...";
1433         
1434         var query = Form.serialize("feed_add_form");
1435         
1436         xmlhttp.open("GET", "backend.php?" + query, true);
1437         xmlhttp.onreadystatechange=dlg_frefresh_callback;
1438         xmlhttp.send(null);
1439
1440         return false;
1441 }
1442
1443 function filterCR(e, f)
1444 {
1445      var key;
1446
1447      if(window.event)
1448           key = window.event.keyCode;     //IE
1449      else
1450           key = e.which;     //firefox
1451
1452         if (key == 13) {
1453                 if (typeof f != 'undefined') {
1454                         f();
1455                         return false;
1456                 } else {
1457                         return false;
1458                 }
1459         } else {
1460                 return true;
1461         }
1462 }
1463
1464 function getMainContext() {
1465         return this.window;
1466 }
1467
1468 function getFeedsContext() {
1469         return this.window;
1470 }
1471
1472 function getContentContext() {
1473         return this.window;
1474 }
1475
1476 function getHeadlinesContext() {
1477         return this.window;
1478 }
1479
1480 var debug_last_class = "even";
1481
1482 function debug(msg) {
1483
1484         if (debug_last_class == "even") {
1485                 debug_last_class = "odd";
1486         } else {
1487                 debug_last_class = "even";
1488         }
1489
1490         var c = document.getElementById('debug_output');
1491         if (c && c.style.display == "block") {
1492                 while (c.lastChild != 'undefined' && c.childNodes.length > 100) {
1493                         c.removeChild(c.lastChild);
1494                 }
1495         
1496                 var d = new Date();
1497                 var ts = leading_zero(d.getHours()) + ":" + leading_zero(d.getMinutes()) +
1498                         ":" + leading_zero(d.getSeconds());
1499                 c.innerHTML = "<li class=\"" + debug_last_class + "\"><span class=\"debugTS\">[" + ts + "]</span> " + 
1500                         msg + "</li>" + c.innerHTML;
1501         }
1502 }
1503
1504 function getInitParam(key) {
1505         return init_params[key];
1506 }
1507
1508 function storeInitParam(key, value) {
1509         debug("<b>storeInitParam is OBSOLETE: " + key + " => " + value + "</b>");
1510 }
1511
1512 function fatalError(code, message) {
1513         try {   
1514
1515                 if (code == 6) {
1516                         window.location.href = "tt-rss.php";                    
1517                 } else if (code == 5) {
1518                         window.location.href = "update.php";
1519                 } else {
1520                         var fe = document.getElementById("fatal_error");
1521                         var fc = document.getElementById("fatal_error_msg");
1522         
1523                         if (message == "") message = "Unknown error";
1524
1525                         fc.innerHTML = "<img src='images/sign_excl.png'> " + message + " (Code " + code + ")";
1526         
1527                         fe.style.display = "block";
1528                 }
1529
1530         } catch (e) {
1531                 exception_error("fatalError", e);
1532         }
1533 }
1534
1535 function getFeedName(id, is_cat) {      
1536         var d = getFeedsContext().document;
1537
1538         var e;
1539
1540         if (is_cat) {
1541                 e = d.getElementById("FCATN-" + id);
1542         } else {
1543                 e = d.getElementById("FEEDN-" + id);
1544         }
1545         if (e) {
1546                 return e.innerHTML.stripTags();
1547         } else {
1548                 return null;
1549         }
1550 }
1551
1552 function viewContentUrl(url) {
1553         getContentContext().location = url;
1554 }
1555
1556 function filterDlgCheckAction(sender) {
1557
1558         try {
1559
1560                 var action = sender[sender.selectedIndex].value;
1561
1562                 var form = document.forms["filter_add_form"];
1563         
1564                 if (!form) {
1565                         form = document.forms["filter_edit_form"];
1566                 }
1567
1568                 if (!form) {
1569                         debug("filterDlgCheckAction: can't find form!");
1570                         return;
1571                 }
1572
1573                 var action_param = form.action_param;
1574
1575                 if (!action_param) {
1576                         debug("filterDlgCheckAction: can't find action param!");
1577                         return;
1578                 }
1579
1580                 // if selected action supports parameters, enable params field
1581                 if (action == 4) {
1582                         action_param.disabled = false;
1583                 } else {
1584                         action_param.disabled = true;
1585                 }
1586
1587         } catch (e) {
1588                 exception_error(e, "filterDlgCheckAction");
1589         }
1590
1591 }
1592
1593 function explainError(code) {
1594         return displayDlg("explainError", code);
1595 }
1596
1597 function logoutUser() {
1598         try {
1599                 if (xmlhttp_ready(xmlhttp_rpc)) {
1600
1601                         notify_progress("Logging out, please wait...", true);
1602
1603                         xmlhttp_rpc.open("GET", "backend.php?op=rpc&subop=logout", true);
1604                         xmlhttp_rpc.onreadystatechange=logout_callback;
1605                         xmlhttp_rpc.send(null);
1606                 } else {
1607                         printLockingError();
1608                 }
1609         } catch (e) {
1610                 exception_error("logoutUser", e);
1611         }
1612 }