]> git.wh0rd.org Git - tt-rss.git/blob - functions.js
use css-based layout in normal mode
[tt-rss.git] / functions.js
1 var hotkeys_enabled = true;
2 var notify_silent = false;
3 var last_progress_point = 0;
4 var sanity_check_done = false;
5
6 /* add method to remove element from array */
7
8 Array.prototype.remove = function(s) {
9         for (var i=0; i < this.length; i++) {
10                 if (s == this[i]) this.splice(i, 1);
11         }
12 }
13
14 /* create console.log if it doesn't exist */
15
16 if (!window.console) console = {};
17 console.log = console.log || function(msg) { };
18 console.warn = console.warn || function(msg) { };
19 console.error = console.error || function(msg) { };
20
21 function exception_error(location, e, ext_info) {
22         var msg = format_exception_error(location, e);
23
24         if (!ext_info) ext_info = false;
25
26         disableHotkeys();
27
28         try {
29
30                 var ebc = $("xebContent");
31         
32                 if (ebc) {
33         
34                         Element.show("dialog_overlay");
35                         Element.show("errorBoxShadow");
36         
37                         if (ext_info) {
38                                 if (ext_info.responseText) {
39                                         ext_info = ext_info.responseText;
40                                 }
41                         }
42         
43                         ebc.innerHTML = 
44                                 "<div><b>Error message:</b></div>" +
45                                 "<pre>" + msg + "</pre>";
46
47                         if (ext_info) {
48                                 ebc.innerHTML += "<div><b>Additional information:</b></div>" +
49                                 "<textarea readonly=\"1\">" + ext_info + "</textarea>";
50                         }
51
52                         ebc.innerHTML += "<div><b>Stack trace:</b></div>" +
53                                 "<textarea readonly=\"1\">" + e.stack + "</textarea>";
54         
55                 } else {
56                         alert(msg);
57                 }
58
59         } catch (e) {
60                 alert(msg);
61
62         }
63
64 }
65
66 function format_exception_error(location, e) {
67         var msg;
68
69         if (e.fileName) {
70                 var base_fname = e.fileName.substring(e.fileName.lastIndexOf("/") + 1);
71         
72                 msg = "Exception: " + e.name + ", " + e.message + 
73                         "\nFunction: " + location + "()" +
74                         "\nLocation: " + base_fname + ":" + e.lineNumber;
75
76         } else if (e.description) {
77                 msg = "Exception: " + e.description + "\nFunction: " + location + "()";
78         } else {
79                 msg = "Exception: " + e + "\nFunction: " + location + "()";
80         }
81
82         console.error("EXCEPTION: " + msg);
83
84         return msg;
85 }
86
87
88 function disableHotkeys() {
89         hotkeys_enabled = false;
90 }
91
92 function enableHotkeys() {
93         hotkeys_enabled = true;
94 }
95
96 function param_escape(arg) {
97         if (typeof encodeURIComponent != 'undefined')
98                 return encodeURIComponent(arg); 
99         else
100                 return escape(arg);
101 }
102
103 function param_unescape(arg) {
104         if (typeof decodeURIComponent != 'undefined')
105                 return decodeURIComponent(arg); 
106         else
107                 return unescape(arg);
108 }
109
110 var notify_hide_timerid = false;
111
112 function hide_notify() {
113         var n = $("notify");
114         if (n) {
115                 n.style.display = "none";
116         }
117
118
119 function notify_silent_next() {
120         notify_silent = true;
121 }
122
123 function notify_real(msg, no_hide, n_type) {
124
125         if (notify_silent) {
126                 notify_silent = false;
127                 return;
128         }
129
130         var n = $("notify");
131         var nb = $("notify_body");
132
133         if (!n || !nb) return;
134
135         if (notify_hide_timerid) {
136                 window.clearTimeout(notify_hide_timerid);
137         }
138
139         if (msg == "") {
140                 if (n.style.display == "block") {
141                         notify_hide_timerid = window.setTimeout("hide_notify()", 0);
142                 }
143                 return;
144         } else {
145                 n.style.display = "block";
146         }
147
148         /* types:
149
150                 1 - generic
151                 2 - progress
152                 3 - error
153                 4 - info
154
155         */
156
157         if (typeof __ != 'undefined') {
158                 msg = __(msg);
159         }
160
161         if (n_type == 1) {
162                 n.className = "notify";
163         } else if (n_type == 2) {
164                 n.className = "notifyProgress";
165                 msg = "<img src='"+getInitParam("sign_progress")+"'> " + msg;
166         } else if (n_type == 3) {
167                 n.className = "notifyError";
168                 msg = "<img src='"+getInitParam("sign_excl")+"'> " + msg;
169         } else if (n_type == 4) {
170                 n.className = "notifyInfo";
171                 msg = "<img src='"+getInitParam("sign_info")+"'> " + msg;
172         }
173
174 //      msg = "<img src='images/live_com_loading.gif'> " + msg;
175
176         nb.innerHTML = msg;
177
178         if (!no_hide) {
179                 notify_hide_timerid = window.setTimeout("hide_notify()", 3000);
180         }
181 }
182
183 function notify(msg, no_hide) {
184         notify_real(msg, no_hide, 1);
185 }
186
187 function notify_progress(msg, no_hide) {
188         notify_real(msg, no_hide, 2);
189 }
190
191 function notify_error(msg, no_hide) {
192         notify_real(msg, no_hide, 3);
193
194 }
195
196 function notify_info(msg, no_hide) {
197         notify_real(msg, no_hide, 4);
198 }
199
200 function cleanSelected(element) {
201         var content = $(element);
202
203         for (i = 0; i < content.rows.length; i++) {
204                 content.rows[i].className = content.rows[i].className.replace("Selected", "");
205         }
206 }
207
208 function setCookie(name, value, lifetime, path, domain, secure) {
209         
210         var d = false;
211         
212         if (lifetime) {
213                 d = new Date();
214                 d.setTime(d.getTime() + (lifetime * 1000));
215         }
216
217         console.log("setCookie: " + name + " => " + value + ": " + d);
218         
219         int_setCookie(name, value, d, path, domain, secure);
220
221 }
222
223 function int_setCookie(name, value, expires, path, domain, secure) {
224         document.cookie= name + "=" + escape(value) +
225                 ((expires) ? "; expires=" + expires.toGMTString() : "") +
226                 ((path) ? "; path=" + path : "") +
227                 ((domain) ? "; domain=" + domain : "") +
228                 ((secure) ? "; secure" : "");
229 }
230
231 function delCookie(name, path, domain) {
232         if (getCookie(name)) {
233                 document.cookie = name + "=" +
234                 ((path) ? ";path=" + path : "") +
235                 ((domain) ? ";domain=" + domain : "" ) +
236                 ";expires=Thu, 01-Jan-1970 00:00:01 GMT";
237         }
238 }
239                 
240
241 function getCookie(name) {
242
243         var dc = document.cookie;
244         var prefix = name + "=";
245         var begin = dc.indexOf("; " + prefix);
246         if (begin == -1) {
247             begin = dc.indexOf(prefix);
248             if (begin != 0) return null;
249         }
250         else {
251             begin += 2;
252         }
253         var end = document.cookie.indexOf(";", begin);
254         if (end == -1) {
255             end = dc.length;
256         }
257         return unescape(dc.substring(begin + prefix.length, end));
258 }
259
260 function gotoPreferences() {
261         document.location.href = "prefs.php";
262 }
263
264 function gotoMain() {
265         document.location.href = "tt-rss.php";
266 }
267
268 function gotoExportOpml() {
269         document.location.href = "opml.php?op=Export";
270 }
271
272
273 /** * @(#)isNumeric.js * * Copyright (c) 2000 by Sundar Dorai-Raj
274   * * @author Sundar Dorai-Raj
275   * * Email: sdoraira@vt.edu
276   * * This program is free software; you can redistribute it and/or
277   * * modify it under the terms of the GNU General Public License 
278   * * as published by the Free Software Foundation; either version 2 
279   * * of the License, or (at your option) any later version, 
280   * * provided that any use properly credits the author. 
281   * * This program is distributed in the hope that it will be useful,
282   * * but WITHOUT ANY WARRANTY; without even the implied warranty of
283   * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
284   * * GNU General Public License for more details at http://www.gnu.org * * */
285
286   var numbers=".0123456789";
287   function isNumeric(x) {
288     // is x a String or a character?
289     if(x.length>1) {
290       // remove negative sign
291       x=Math.abs(x)+"";
292       for(j=0;j<x.length;j++) {
293         // call isNumeric recursively for each character
294         number=isNumeric(x.substring(j,j+1));
295         if(!number) return number;
296       }
297       return number;
298     }
299     else {
300       // if x is number return true
301       if(numbers.indexOf(x)>=0) return true;
302       return false;
303     }
304   }
305
306
307 function toggleSelectRowById(sender, id) {
308         var row = $(id);
309
310         if (sender.checked) {
311                 if (!row.className.match("Selected")) {
312                         row.className = row.className + "Selected";
313                 }
314         } else {
315                 if (row.className.match("Selected")) {
316                         row.className = row.className.replace("Selected", "");
317                 }
318         }
319 }
320
321 function toggleSelectListRow(sender) {
322         var parent_row = sender.parentNode;
323
324         if (sender.checked) {
325                 if (!parent_row.className.match("Selected")) {
326                         parent_row.className = parent_row.className + "Selected";
327                 }
328         } else {
329                 if (parent_row.className.match("Selected")) {
330                         parent_row.className = parent_row.className.replace("Selected", "");
331                 }
332         }
333 }
334
335 function tSR(sender) {
336         return toggleSelectRow(sender);
337 }
338
339 function toggleSelectRow(sender) {
340         var parent_row = sender.parentNode.parentNode;
341
342         if (sender.checked) {
343                 if (!parent_row.className.match("Selected")) {
344                         parent_row.className = parent_row.className + "Selected";
345                 }
346         } else {
347                 if (parent_row.className.match("Selected")) {
348                         parent_row.className = parent_row.className.replace("Selected", "");
349                 }
350         }
351 }
352
353 function checkboxToggleElement(elem, id) {
354         if (elem.checked) {
355                 Effect.Appear(id, {duration : 0.5});
356         } else {
357                 Effect.Fade(id, {duration : 0.5});
358         }
359 }
360
361 function dropboxSelect(e, v) {
362         for (i = 0; i < e.length; i++) {
363                 if (e[i].value == v) {
364                         e.selectedIndex = i;
365                         break;
366                 }
367         }
368 }
369
370 // originally stolen from http://www.11tmr.com/11tmr.nsf/d6plinks/MWHE-695L9Z
371 // bugfixed just a little bit :-)
372 function getURLParam(strParamName){
373   var strReturn = "";
374   var strHref = window.location.href;
375
376   if (strHref.indexOf("#") == strHref.length-1) {
377                 strHref = strHref.substring(0, strHref.length-1);
378   }
379
380   if ( strHref.indexOf("?") > -1 ){
381     var strQueryString = strHref.substr(strHref.indexOf("?"));
382     var aQueryString = strQueryString.split("&");
383     for ( var iParam = 0; iParam < aQueryString.length; iParam++ ){
384       if (aQueryString[iParam].indexOf(strParamName + "=") > -1 ){
385         var aParam = aQueryString[iParam].split("=");
386         strReturn = aParam[1];
387         break;
388       }
389     }
390   }
391   return strReturn;
392
393
394 function leading_zero(p) {
395         var s = String(p);
396         if (s.length == 1) s = "0" + s;
397         return s;
398 }
399
400 function make_timestamp() {
401         var d = new Date();
402
403         return leading_zero(d.getHours()) + ":" + leading_zero(d.getMinutes()) +
404                         ":" + leading_zero(d.getSeconds());
405 }
406
407
408 function closeErrorBox() {
409
410         if (Element.visible("errorBoxShadow")) {
411                 Element.hide("dialog_overlay");
412                 Element.hide("errorBoxShadow");
413
414                 enableHotkeys();
415         }
416
417         return false;
418 }
419
420 function closeInfoBox(cleanup) {
421
422         try {
423                 enableHotkeys();
424
425                 if (Element.visible("infoBoxShadow")) {
426                         Element.hide("dialog_overlay");
427                         Element.hide("infoBoxShadow");
428
429                         if (cleanup) $("infoBox").innerHTML = "&nbsp;";
430                 }
431         } catch (e) {
432                 exception_error("closeInfoBox", e);
433         }
434         
435         return false;
436 }
437
438
439 function displayDlg(id, param, callback) {
440
441         notify_progress("Loading, please wait...", true);
442
443         disableHotkeys();
444
445         var query = "?op=dlg&id=" +
446                 param_escape(id) + "&param=" + param_escape(param);
447
448         new Ajax.Request("backend.php", {
449                 parameters: query,
450                 onComplete: function (transport) {
451                         infobox_callback2(transport);
452                         if (callback) callback(transport);
453                 } });
454
455         return false;
456 }
457
458 function infobox_submit_callback2(transport) {
459         closeInfoBox();
460
461         try {
462                 // called from prefs, reload tab
463                 if (typeof active_tab != 'undefined' && active_tab) {
464                         selectTab(active_tab, false);
465                 }
466         } catch (e) { }
467
468         if (transport.responseText) {
469                 notify_info(transport.responseText);
470         }
471 }
472
473 function infobox_callback2(transport) {
474         try {
475
476                 console.log("infobox_callback2");
477
478                 var box = $('infoBox');
479                 
480                 if (box) {                      
481
482                         if (!getInitParam("infobox_disable_overlay")) {
483                                 Element.show("dialog_overlay");
484                         }
485
486                         box.innerHTML=transport.responseText;                   
487                         Element.show("infoBoxShadow");
488                         //Effect.SlideDown("infoBoxShadow", {duration : 1.0});
489
490
491                 }
492
493                 disableHotkeys();
494
495                 notify("");
496         } catch (e) {
497                 exception_error("infobox_callback2", e);
498         }
499 }
500
501 function createFilter() {
502
503         try {
504
505                 var form = document.forms['filter_add_form'];
506                 var reg_exp = form.reg_exp.value;
507         
508                 if (reg_exp == "") {
509                         alert(__("Can't add filter: nothing to match on."));
510                         return false;
511                 }
512
513                 var query = "?op=rpc&subop=verifyRegexp&reg_exp=" + param_escape(reg_exp);
514
515                 notify_progress("Verifying regular expression...");
516
517                 new Ajax.Request("backend.php", {
518                                 parameters: query,
519                                 onComplete: function(transport) {
520                                         handle_rpc_reply(transport);
521
522                                         var response = transport.responseXML;
523
524                                         if (response) {
525                                                 var s = response.getElementsByTagName("status")[0].firstChild.nodeValue;
526         
527                                                 notify('');
528
529                                                 if (s == "INVALID") {
530                                                         alert("Match regular expression seems to be invalid.");
531                                                         return;
532                                                 } else {
533
534                                                         var query = Form.serialize("filter_add_form");
535                                                 
536                                                         // we can be called from some other tab in Prefs                
537                                                         if (typeof active_tab != 'undefined' && active_tab) {
538                                                                 active_tab = "filterConfig";
539                                                         }
540                                                 
541                                                         new Ajax.Request("backend.php?" + query, {
542                                                                 onComplete: function (transport) {
543                                                                         infobox_submit_callback2(transport);
544                                                                 } });
545                                                         
546                                                         return true;
547                                                 }
548                                         }
549
550                         } });
551
552         } catch (e) {
553                 exception_error("createFilter", e);
554         }
555 }
556
557 function isValidURL(s) {
558         return s.match("http://") != null || s.match("https://") != null || s.match("feed://") != null;
559 }
560
561 function subscribeToFeed() {
562
563         try {
564
565         var form = document.forms['feed_add_form'];
566         var feed_url = form.feed_url.value;
567
568         if (feed_url == "") {
569                 alert(__("Can't subscribe: no feed URL given."));
570                 return false;
571         }
572
573         notify_progress(__("Subscribing to feed..."), true);
574
575         var query = Form.serialize("feed_add_form");
576         
577         console.log("subscribe q: " + query);
578
579         Form.disable("feed_add_form");
580
581         new Ajax.Request("backend.php", {
582                 parameters: query,
583                 onComplete: function(transport) { 
584                         //dlg_frefresh_callback(transport); 
585
586                         try {
587
588                                 if (!transport.responseXML)
589                                         console.log(transport.responseText);
590
591                                 var result = transport.responseXML.getElementsByTagName('result')[0];
592                                 var rc = parseInt(result.getAttribute('code'));
593         
594                                 Form.enable("feed_add_form");
595         
596                                 notify('');
597         
598                                 switch (rc) {
599                                 case 1:
600                                         closeInfoBox();
601                                         notify_info(__("Subscribed to %s").replace("%s", feed_url));
602         
603                                         if (inPreferences()) {
604                                                 updateFeedList();
605                                         } else {
606                                                 setTimeout('updateFeedList(false, false)', 50);
607                                         }
608                                         break;
609                                 case 2:
610                                         alert(__("Specified URL seems to be invalid."));
611                                         break;
612                                 case 3:
613                                         alert(__("Specified URL doesn't seem to contain any feeds."));
614                                         break;
615                                 case 4:
616                                         new Ajax.Request("backend.php", {
617                                                 parameters: 'op=rpc&subop=extractfeedurls&url=' + encodeURIComponent(feed_url),
618                                                 onComplete: function(transport) {
619                                                         var result = transport.responseXML.getElementsByTagName('urls')[0];
620                                                         var feeds = JSON.parse(result.firstChild.nodeValue);
621                                                         var select = document.getElementById("faad_feeds_container_select");
622         
623                                                         while (select.hasChildNodes()) {
624                                                                 select.removeChild(elem.firstChild);
625                                                         }
626                                                         var count = 0;
627                                                         for (var feedUrl in feeds) {
628                                                                 select.insert(new Option(feeds[feedUrl], feedUrl, false));
629                                                                 count++;
630                                                         }
631                                                         if (count > 5) count = 5;
632                                                         select.size = count;
633         
634                                                         Effect.Appear('fadd_feeds_container', {duration : 0.5});
635                                                 }
636                                         });
637                                         break;
638                                 case 5:
639                                         alert(__("Couldn't download the specified URL."));
640                                         break;
641                                 case 0:
642                                         alert(__("You are already subscribed to this feed."));
643                                         break;
644                                 }
645
646                         } catch (e) {
647                                 exception_error("subscribeToFeed", e);
648                         }
649
650                 } });
651
652         } catch (e) {
653                 exception_error("subscribeToFeed", e);
654         }
655
656         return false;
657 }
658
659 function filterCR(e, f)
660 {
661      var key;
662
663      if(window.event)
664           key = window.event.keyCode;     //IE
665      else
666           key = e.which;     //firefox
667
668         if (key == 13) {
669                 if (typeof f != 'undefined') {
670                         f();
671                         return false;
672                 } else {
673                         return false;
674                 }
675         } else {
676                 return true;
677         }
678 }
679
680 function getInitParam(key) {
681         return init_params[key];
682 }
683
684 function setInitParam(key, value) {
685         init_params[key] = value;
686 }
687
688 function fatalError(code, msg, ext_info) {
689         try {   
690
691                 if (!ext_info) ext_info = "N/A";
692
693                 if (code == 6) {
694                         window.location.href = "tt-rss.php";                    
695                 } else if (code == 5) {
696                         window.location.href = "db-updater.php";
697                 } else {
698         
699                         if (msg == "") msg = "Unknown error";
700
701                         var ebc = $("xebContent");
702         
703                         if (ebc) {
704         
705                                 Element.show("dialog_overlay");
706                                 Element.show("errorBoxShadow");
707                                 Element.hide("xebBtn");
708
709                                 if (ext_info) {
710                                         if (ext_info.responseText) {
711                                                 ext_info = ext_info.responseText;
712                                         }
713                                 }
714         
715                                 ebc.innerHTML = 
716                                         "<div><b>Error message:</b></div>" +
717                                         "<pre>" + msg + "</pre>" +
718                                         "<div><b>Additional information:</b></div>" +
719                                         "<textarea readonly=\"1\">" + ext_info + "</textarea>";
720                         }
721                 }
722
723         } catch (e) {
724                 exception_error("fatalError", e);
725         }
726 }
727
728 function filterDlgCheckType(sender) {
729
730         try {
731
732                 var ftype = sender[sender.selectedIndex].value;
733
734                 var form = document.forms["filter_add_form"];
735         
736                 if (!form) {
737                         form = document.forms["filter_edit_form"];
738                 }
739
740                 if (!form) {
741                         console.log("filterDlgCheckType: can't find form!");
742                         return;
743                 }
744
745                 // if selected filter type is 5 (Date) enable the modifier dropbox
746                 if (ftype == 5) {
747                         Element.show("filter_dlg_date_mod_box");
748                         Element.show("filter_dlg_date_chk_box");
749                 } else {
750                         Element.hide("filter_dlg_date_mod_box");
751                         Element.hide("filter_dlg_date_chk_box");
752
753                 }
754
755         } catch (e) {
756                 exception_error("filterDlgCheckType", e);
757         }
758
759 }
760
761 function filterDlgCheckAction(sender) {
762
763         try {
764
765                 var action = sender[sender.selectedIndex].value;
766
767                 var form = document.forms["filter_add_form"];
768         
769                 if (!form) {
770                         form = document.forms["filter_edit_form"];
771                 }
772
773                 if (!form) {
774                         console.log("filterDlgCheckAction: can't find form!");
775                         return;
776                 }
777
778                 var action_param = $("filter_dlg_param_box");
779
780                 if (!action_param) {
781                         console.log("filterDlgCheckAction: can't find action param box!");
782                         return;
783                 }
784
785                 // if selected action supports parameters, enable params field
786                 if (action == 4 || action == 6 || action == 7) {
787                         Element.show(action_param);
788                         if (action != 7) {
789                                 Element.show(form.action_param);
790                                 Element.hide(form.action_param_label);
791                         } else {
792                                 Element.show(form.action_param_label);
793                                 Element.hide(form.action_param);
794                         }
795                 } else {
796                         Element.hide(action_param);
797                 }
798
799         } catch (e) {
800                 exception_error("filterDlgCheckAction", e);
801         }
802
803 }
804
805 function filterDlgCheckDate() {
806         try {
807                 var form = document.forms["filter_add_form"];
808         
809                 if (!form) {
810                         form = document.forms["filter_edit_form"];
811                 }
812
813                 if (!form) {
814                         console.log("filterDlgCheckAction: can't find form!");
815                         return;
816                 }
817
818                 var reg_exp = form.reg_exp.value;
819
820                 var query = "?op=rpc&subop=checkDate&date=" + reg_exp;
821
822                 new Ajax.Request("backend.php", {
823                         parameters: query,
824                         onComplete: function(transport) { 
825
826                                 var form = document.forms["filter_add_form"];
827         
828                                 if (!form) {
829                                         form = document.forms["filter_edit_form"];
830                                 }
831
832                                 if (transport.responseXML) {
833                                         var result = transport.responseXML.getElementsByTagName("result")[0];
834
835                                         if (result && result.firstChild) {
836                                                 if (result.firstChild.nodeValue == "1") {
837
838                                                         new Effect.Highlight(form.reg_exp, {startcolor : '#00ff00'});
839
840                                                         return;
841                                                 }
842                                         }
843                                 }
844
845                                 new Effect.Highlight(form.reg_exp, {startcolor : '#ff0000'});
846
847                         } });
848
849
850         } catch (e) {
851                 exception_error("filterDlgCheckDate", e);
852         }
853 }
854
855 function explainError(code) {
856         return displayDlg("explainError", code);
857 }
858
859 function displayHelpInfobox(topic_id) {
860
861         var url = "backend.php?op=help&tid=" + param_escape(topic_id);
862
863         var w = window.open(url, "ttrss_help", 
864                 "status=0,toolbar=0,location=0,width=450,height=500,scrollbars=1,menubar=0");
865
866 }
867
868 function loading_set_progress(p) {
869         try {
870                 if (p < last_progress_point || !Element.visible("overlay")) return;
871
872                 console.log("loading_set_progress : " + p + " (" + last_progress_point + ")");
873
874                 var o = $("l_progress_i");
875
876 //              o.style.width = (p * 2) + "px";
877
878                 new Effect.Scale(o, p, { 
879                         scaleY : false,
880                         scaleFrom : last_progress_point,
881                         scaleMode: { originalWidth : 200 },
882                         queue: { position: 'end', scope: 'LSP-Q', limit: 3 } }); 
883
884                 last_progress_point = p;
885
886         } catch (e) {
887                 exception_error("loading_set_progress", e);
888         }
889 }
890
891 function remove_splash() {
892         if (Element.visible("overlay")) {
893                 console.log("about to remove splash, OMG!");
894                 Element.hide("overlay");
895                 console.log("removed splash!");
896         }
897 }
898
899 function getSelectedFeedsFromBrowser() {
900
901         var list = $("browseFeedList");
902
903         var selected = new Array();
904         
905         for (i = 0; i < list.childNodes.length; i++) {
906                 var child = list.childNodes[i];
907                 if (child.id && child.id.match("FBROW-")) {
908                         var id = child.id.replace("FBROW-", "");
909                         
910                         var cb = $("FBCHK-" + id);
911
912                         if (cb.checked) {
913                                 selected.push(id);
914                         }
915                 }
916         }
917
918         return selected;
919 }
920
921 function updateFeedBrowser() {
922         try {
923
924                 var query = Form.serialize("feed_browser");
925
926                 Element.show('feed_browser_spinner');
927
928                 new Ajax.Request("backend.php", {
929                         parameters: query,
930                         onComplete: function(transport) { 
931                                 notify('');
932
933                                 Element.hide('feed_browser_spinner');
934
935                                 var c = $("browseFeedList");
936                                 var r = transport.responseXML.getElementsByTagName("content")[0];
937                                 var nr = transport.responseXML.getElementsByTagName("num-results")[0];
938                                 var mode = transport.responseXML.getElementsByTagName("mode")[0];
939
940                                 if (c && r) {
941                                         c.innerHTML = r.firstChild.nodeValue;
942                                 }
943
944                                 if (parseInt(mode.getAttribute("value")) == 2) {
945                                         Element.show('feed_archive_remove');
946                                 } else {
947                                         Element.hide('feed_archive_remove');
948                                 }
949         
950                         } });
951
952         } catch (e) {
953                 exception_error("updateFeedBrowser", e);
954         }
955
956 }
957
958 function transport_error_check(transport) {
959         try {
960                 if (transport.responseXML) {
961                         var error = transport.responseXML.getElementsByTagName("error")[0];
962
963                         if (error) {
964                                 var code = error.getAttribute("error-code");
965                                 var msg = error.getAttribute("error-msg");
966                                 if (code != 0) {
967                                         fatalError(code, msg);
968                                         return false;
969                                 }
970                         }
971                 }
972         } catch (e) {
973                 exception_error("check_for_error_xml", e);
974         }
975         return true;
976 }
977
978 function strip_tags(s) {
979         return s.replace(/<\/?[^>]+(>|$)/g, "");
980 }
981
982 function truncate_string(s, length) {
983         if (!length) length = 30;
984         var tmp = s.substring(0, length);
985         if (s.length > length) tmp += "&hellip;";
986         return tmp;
987 }
988
989 function hotkey_prefix_timeout() {
990         try {
991
992                 var date = new Date();
993                 var ts = Math.round(date.getTime() / 1000);
994
995                 if (hotkey_prefix_pressed && ts - hotkey_prefix_pressed >= 5) {
996                         console.log("hotkey_prefix seems to be stuck, aborting");
997                         hotkey_prefix_pressed = false;
998                         hotkey_prefix = false;
999                         Element.hide('cmdline');
1000                 }
1001
1002                 setTimeout("hotkey_prefix_timeout()", 1000);
1003
1004         } catch  (e) {
1005                 exception_error("hotkey_prefix_timeout", e);
1006         }
1007 }
1008
1009 function hideAuxDlg() {
1010         try {
1011                 Element.hide('auxDlg');
1012         } catch (e) {
1013                 exception_error("hideAuxDlg", e);
1014         }
1015 }
1016
1017 function feedBrowserSubscribe() {
1018         try {
1019
1020                 var selected = getSelectedFeedsFromBrowser();
1021
1022                 var mode = document.forms['feed_browser'].mode;
1023
1024                 mode = mode[mode.selectedIndex].value;
1025
1026                 if (selected.length > 0) {
1027                         closeInfoBox();
1028
1029                         notify_progress("Loading, please wait...", true);
1030
1031                         var query = "?op=rpc&subop=massSubscribe&ids="+
1032                                 param_escape(selected.toString()) + "&mode=" + param_escape(mode);
1033
1034                         new Ajax.Request("backend.php", {
1035                                 parameters: query,
1036                                 onComplete: function(transport) { 
1037
1038                                         var nf = transport.responseXML.getElementsByTagName('num-feeds')[0];
1039                                         var nf_value = nf.getAttribute("value");
1040
1041                                         notify_info(__("Subscribed to %d feed(s).").replace("%d", nf_value));
1042
1043                                         if (inPreferences()) {
1044                                                 updateFeedList();
1045                                         } else {
1046                                                 setTimeout('updateFeedList(false, false)', 50);
1047                                         }
1048                                 } });
1049
1050                 } else {
1051                         alert(__("No feeds are selected."));
1052                 }
1053
1054         } catch (e) {
1055                 exception_error("feedBrowserSubscribe", e);
1056         }
1057 }
1058
1059 function feedArchiveRemove() {
1060         try {
1061
1062                 var selected = getSelectedFeedsFromBrowser();
1063
1064                 if (selected.length > 0) {
1065
1066                         var pr = __("Remove selected feeds from the archive? Feeds with stored articles will not be removed.");
1067
1068                         if (confirm(pr)) {
1069                                 Element.show('feed_browser_spinner');
1070
1071                                 var query = "?op=rpc&subop=remarchived&ids=" + 
1072                                         param_escape(selected.toString());;
1073
1074                                 new Ajax.Request("backend.php", {
1075                                         parameters: query,
1076                                         onComplete: function(transport) { 
1077                                                 updateFeedBrowser();
1078                                         } }); 
1079                         }
1080
1081                 } else {
1082                         alert(__("No feeds are selected."));
1083                 }
1084
1085         } catch (e) {
1086                 exception_error("feedArchiveRemove", e);
1087         }
1088 }
1089
1090 function uploadIconHandler(rc) {
1091         try {
1092                 switch (rc) {
1093                         case 0:
1094                                 notify_info("Upload complete.");
1095                                 if (inPreferences()) {
1096                                         updateFeedList();
1097                                 } else {
1098                                         setTimeout('updateFeedList(false, false)', 50);
1099                                 }
1100                                 break;
1101                         case 1:
1102                                 notify_error("Upload failed: icon is too big.");
1103                                 break;
1104                         case 2:
1105                                 notify_error("Upload failed.");
1106                                 break;
1107                 }
1108
1109         } catch (e) {
1110                 exception_error("uploadIconHandler", e);
1111         }
1112 }
1113
1114 function removeFeedIcon(id) {
1115
1116         try {
1117
1118                 if (confirm(__("Remove stored feed icon?"))) {
1119                         var query = "backend.php?op=pref-feeds&subop=removeicon&feed_id=" + param_escape(id);
1120
1121                         console.log(query);
1122
1123                         notify_progress("Removing feed icon...", true);
1124
1125                         new Ajax.Request("backend.php", {
1126                                 parameters: query,
1127                                 onComplete: function(transport) { 
1128                                         notify_info("Feed icon removed.");
1129                                         if (inPreferences()) {
1130                                                 updateFeedList();
1131                                         } else {
1132                                                 setTimeout('updateFeedList(false, false)', 50);
1133                                         }
1134                                 } }); 
1135                 }
1136
1137                 return false;
1138         } catch (e) {
1139                 exception_error("uploadFeedIcon", e);
1140         }
1141 }
1142
1143 function uploadFeedIcon() {
1144
1145         try {
1146
1147                 var file = $("icon_file");
1148
1149                 if (file.value.length == 0) {
1150                         alert(__("Please select an image file to upload."));
1151                 } else {
1152                         if (confirm(__("Upload new icon for this feed?"))) {
1153                                 notify_progress("Uploading, please wait...", true);
1154                                 return true;
1155                         }
1156                 }
1157
1158                 return false;
1159
1160         } catch (e) {
1161                 exception_error("uploadFeedIcon", e);
1162         }
1163 }
1164
1165 function addLabel(select, callback) {
1166
1167         try {
1168
1169                 var caption = prompt(__("Please enter label caption:"), "");
1170
1171                 if (caption != undefined) {
1172         
1173                         if (caption == "") {
1174                                 alert(__("Can't create label: missing caption."));
1175                                 return false;
1176                         }
1177
1178                         var query = "?op=pref-labels&subop=add&caption=" + 
1179                                 param_escape(caption);
1180
1181                         if (select)
1182                                 query += "&output=select";
1183
1184                         notify_progress("Loading, please wait...", true);
1185
1186                         if (inPreferences() && !select) active_tab = "labelConfig";
1187
1188                         new Ajax.Request("backend.php", {
1189                                 parameters: query,
1190                                 onComplete: function(transport) { 
1191                                         if (callback) {
1192                                                 callback(transport);
1193                                         } else if (inPreferences()) {
1194                                                 infobox_submit_callback2(transport);
1195                                         } else {
1196                                                 updateFeedList();
1197                                         }
1198                         } });
1199
1200                 }
1201
1202         } catch (e) {
1203                 exception_error("addLabel", e);
1204         }
1205 }
1206
1207 function quickAddFeed() {
1208         displayDlg('quickAddFeed', '',
1209            function () {$('feed_url').focus();});
1210 }
1211
1212 function quickAddFilter() {
1213         displayDlg('quickAddFilter', '',
1214            function () {document.forms['filter_add_form'].reg_exp.focus();});
1215 }
1216
1217 function unsubscribeFeed(feed_id, title) {
1218
1219         var msg = __("Unsubscribe from %s?").replace("%s", title);
1220
1221         if (title == undefined || confirm(msg)) {
1222                 notify_progress("Removing feed...");
1223
1224                 var query = "?op=pref-feeds&quiet=1&subop=remove&ids=" + feed_id;
1225
1226                 new Ajax.Request("backend.php", {
1227                         parameters: query,
1228                         onComplete: function(transport) {
1229
1230                                         closeInfoBox();
1231
1232                                         if (inPreferences()) {
1233                                                 updateFeedList();                               
1234                                         } else {
1235                                                 dlg_frefresh_callback(transport, feed_id);
1236                                         }
1237
1238                                 } });
1239         }
1240
1241         return false;
1242 }
1243
1244
1245 function backend_sanity_check_callback(transport) {
1246
1247         try {
1248
1249                 if (sanity_check_done) {
1250                         fatalError(11, "Sanity check request received twice. This can indicate "+
1251                       "presence of Firebug or some other disrupting extension. "+
1252                                 "Please disable it and try again.");
1253                         return;
1254                 }
1255
1256                 if (!transport.responseXML) {
1257                         if (!store) {
1258                                 fatalError(3, "Sanity check: Received reply is not XML", 
1259                                         transport.responseText);
1260                                 return;
1261                         } else {
1262                                 init_offline();
1263                                 return;
1264                         }
1265                 }
1266
1267                 if (getURLParam("offline")) {
1268                         return init_offline();
1269                 }
1270
1271                 var reply = transport.responseXML.getElementsByTagName("error")[0];
1272
1273                 if (!reply) {
1274                         fatalError(3, "Sanity check: invalid RPC reply", transport.responseText);
1275                         return;
1276                 }
1277
1278                 var error_code = reply.getAttribute("error-code");
1279         
1280                 if (error_code && error_code != 0) {
1281                         return fatalError(error_code, reply.getAttribute("error-msg"));
1282                 }
1283
1284                 console.log("sanity check ok");
1285
1286                 var params = transport.responseXML.getElementsByTagName("init-params")[0];
1287
1288                 if (params) {
1289                         console.log('reading init-params...');
1290
1291                         params = JSON.parse(params.firstChild.nodeValue);
1292
1293                         if (params) {
1294                                 for (k in params) {
1295         
1296                                         var v = params[k];
1297         
1298                                         console.log("IP: " + k + " => " + v);
1299
1300                                         if (db) {
1301                                                 db.execute("DELETE FROM init_params WHERE key = ?", [k]);
1302                                                 db.execute("INSERT INTO init_params (key,value) VALUES (?, ?)",
1303                                                         [k, v]);
1304                                         }
1305                                 }
1306                         }
1307
1308                         init_params = params;
1309                 }
1310
1311                 sanity_check_done = true;
1312
1313                 init_second_stage();
1314
1315         } catch (e) {
1316                 exception_error("backend_sanity_check_callback", e, transport); 
1317         } 
1318 }
1319
1320 function has_local_storage() {
1321         return false;
1322 /*      try {
1323                 return 'localStorage' in window && window['localStorage'] != null;
1324         } catch (e) {
1325                 return false;
1326         } */
1327 }
1328
1329 function catSelectOnChange(elem) {
1330         try {
1331                 var value = elem[elem.selectedIndex].value;
1332                 var def = elem.getAttribute('default');
1333
1334                 if (value == "ADD_CAT") {
1335
1336                         if (def)
1337                                 dropboxSelect(elem, def);
1338                         else
1339                                 elem.selectedIndex = 0;
1340
1341                         quickAddCat(elem);
1342                 }
1343
1344         } catch (e) {
1345                 exception_error("catSelectOnChange", e);
1346         }
1347 }
1348
1349 function quickAddCat(elem) {
1350         try {
1351                 var cat = prompt(__("Please enter category title:"));
1352
1353                 if (cat) {
1354
1355                         var query = "?op=rpc&subop=quickAddCat&cat=" + param_escape(cat);
1356
1357                         notify_progress("Loading, please wait...", true);
1358
1359                         new Ajax.Request("backend.php", {
1360                                 parameters: query,
1361                                 onComplete: function (transport) {
1362                                         var response = transport.responseXML;
1363                                         var select = response.getElementsByTagName("select")[0];
1364                                         var options = select.getElementsByTagName("option");
1365
1366                                         dropbox_replace_options(elem, options);
1367
1368                                         notify('');
1369
1370                         } });
1371
1372                 }
1373
1374         } catch (e) {
1375                 exception_error("quickAddCat", e);
1376         }
1377 }
1378
1379 function genUrlChangeKey(feed, is_cat) {
1380
1381         try {
1382                 var ok = confirm(__("Generate new syndication address for this feed?"));
1383         
1384                 if (ok) {
1385         
1386                         notify_progress("Trying to change address...", true);
1387         
1388                         var query = "?op=rpc&subop=regenFeedKey&id=" + param_escape(feed) + 
1389                                 "&is_cat=" + param_escape(is_cat);
1390
1391                         new Ajax.Request("backend.php", {
1392                                 parameters: query,
1393                                 onComplete: function(transport) {
1394                                                 var new_link = transport.responseXML.getElementsByTagName("link")[0];
1395         
1396                                                 var e = $('gen_feed_url');
1397         
1398                                                 if (new_link) {
1399                                                         
1400                                                         new_link = new_link.firstChild.nodeValue;
1401
1402                                                         e.innerHTML = e.innerHTML.replace(/\&amp;key=.*$/, 
1403                                                                 "&amp;key=" + new_link);
1404
1405                                                         e.href = e.href.replace(/\&amp;key=.*$/,
1406                                                                 "&amp;key=" + new_link);
1407
1408                                                         new Effect.Highlight(e);
1409
1410                                                         notify('');
1411         
1412                                                 } else {
1413                                                         notify_error("Could not change feed URL.");
1414                                                 }
1415                                 } });
1416                 }
1417         } catch (e) {
1418                 exception_error("genUrlChangeKey", e);
1419         }
1420         return false;
1421 }
1422
1423 function labelSelectOnChange(elem) {
1424         try {
1425                 var value = elem[elem.selectedIndex].value;
1426                 var def = elem.getAttribute('default');
1427
1428                 if (value == "ADD_LABEL") {
1429
1430                         if (def)
1431                                 dropboxSelect(elem, def);
1432                         else
1433                                 elem.selectedIndex = 0;
1434
1435                         addLabel(elem, function(transport) {
1436
1437                                         try {
1438
1439                                                 var response = transport.responseXML;
1440                                                 var select = response.getElementsByTagName("select")[0];
1441                                                 var options = select.getElementsByTagName("option");
1442
1443                                                 dropbox_replace_options(elem, options);
1444
1445                                                 notify('');
1446                                         } catch (e) {
1447                                                 exception_error("addLabel", e);
1448                                         }
1449                         });
1450                 }
1451
1452         } catch (e) {
1453                 exception_error("labelSelectOnChange", e);
1454         }
1455 }
1456
1457 function dropbox_replace_options(elem, options) {
1458
1459         try {
1460                 while (elem.hasChildNodes())
1461                         elem.removeChild(elem.firstChild);
1462
1463                 var sel_idx = -1;
1464
1465                 for (var i = 0; i < options.length; i++) {
1466                         var text = options[i].firstChild.nodeValue;
1467                         var value = options[i].getAttribute("value");
1468
1469                         if (value == undefined) value = text;
1470
1471                         var issel = options[i].getAttribute("selected") == "1";
1472
1473                         var option = new Option(text, value, issel);
1474
1475                         if (options[i].getAttribute("disabled"))
1476                                 option.setAttribute("disabled", true);
1477
1478                         elem.insert(option);
1479
1480                         if (issel) sel_idx = i;
1481                 }
1482
1483                 // Chrome doesn't seem to just select stuff when you pass new Option(x, y, true)
1484                 if (sel_idx >= 0) elem.selectedIndex = sel_idx;
1485
1486         } catch (e) {
1487                 exception_error("dropbox_replace_options", e);
1488         }
1489 }
1490
1491 // mode = all, none, invert
1492 function selectTableRows(id, mode) {
1493         try {
1494                 var rows = $(id).rows;
1495
1496                 for (var i = 0; i < rows.length; i++) {
1497                         var row = rows[i];
1498                         var cb = false;
1499
1500                         if (row.id && row.className) {
1501                                 var bare_id = row.id.replace(/^[A-Z]*?-/, "");
1502                                 var inputs = rows[i].getElementsByTagName("input");
1503
1504                                 for (var j = 0; j < inputs.length; j++) {
1505                                         var input = inputs[j];
1506
1507                                         if (input.getAttribute("type") == "checkbox" && 
1508                                                         input.id.match(bare_id)) {
1509
1510                                                 cb = input;
1511                                                 break;
1512                                         }
1513                                 }
1514
1515                                 if (cb) {
1516                                         var issel = row.className.match("Selected");
1517
1518                                         if (mode == "all" && !issel) {
1519                                                 row.className += "Selected";
1520                                                 cb.checked = true;
1521                                         } else if (mode == "none" && issel) {
1522                                                 row.className = row.className.replace("Selected", "");
1523                                                 cb.checked = false;
1524                                         } else if (mode == "invert") {
1525
1526                                                 if (issel) {
1527                                                         row.className = row.className.replace("Selected", "");
1528                                                         cb.checked = false;
1529                                                 } else {
1530                                                         row.className += "Selected";
1531                                                         cb.checked = true;
1532                                                 }
1533                                         }
1534                                 }
1535                         }
1536                 }
1537
1538         } catch (e) {
1539                 exception_error("selectTableRows", e);
1540
1541         }
1542 }
1543
1544 function getSelectedTableRowIds(id) {
1545         var rows = [];
1546
1547         try {
1548                 var elem_rows = $(id).rows;
1549
1550                 for (i = 0; i < elem_rows.length; i++) {
1551                         if (elem_rows[i].className.match("Selected")) {
1552                                 var bare_id = elem_rows[i].id.replace(/^[A-Z]*?-/, "");
1553                                 rows.push(bare_id);
1554                         }
1555                 }
1556
1557         } catch (e) {
1558                 exception_error("getSelectedTableRowIds", e);
1559         }
1560
1561         return rows;
1562 }
1563