]> git.wh0rd.org Git - tt-rss.git/blob - js/functions.js
cdmClicked: do not use event.target.parents to figure out if clicked on a heading
[tt-rss.git] / js / functions.js
1 var loading_progress = 0;
2 var sanity_check_done = false;
3 var init_params = {};
4 var _label_base_index = -1024;
5 var notify_hide_timerid = false;
6
7 Ajax.Base.prototype.initialize = Ajax.Base.prototype.initialize.wrap(
8         function (callOriginal, options) {
9
10                 if (getInitParam("csrf_token") != undefined) {
11                         Object.extend(options, options || { });
12
13                         if (Object.isString(options.parameters))
14                                 options.parameters = options.parameters.toQueryParams();
15                         else if (Object.isHash(options.parameters))
16                                 options.parameters = options.parameters.toObject();
17
18                         options.parameters["csrf_token"] = getInitParam("csrf_token");
19                 }
20
21                 return callOriginal(options);
22         }
23 );
24
25 /* add method to remove element from array */
26
27 Array.prototype.remove = function(s) {
28         for (var i=0; i < this.length; i++) {
29                 if (s == this[i]) this.splice(i, 1);
30         }
31 };
32
33
34 function report_error(message, filename, lineno, colno, error) {
35         exception_error(error, null, filename, lineno);
36 }
37
38 function exception_error(e, e_compat, filename, lineno, colno) {
39         if (typeof e == "string") e = e_compat;
40
41         if (!e) return; // no exception object, nothing to report.
42
43         try {
44                 console.error(e);
45                 var msg = e.toString();
46
47                 try {
48                         new Ajax.Request("backend.php", {
49                                 parameters: {op: "rpc", method: "log",
50                                         file: e.fileName ? e.fileName : filename,
51                                         line: e.lineNumber ? e.lineNumber : lineno,
52                                         msg: msg, context: e.stack},
53                                 onComplete: function (transport) {
54                                         console.warn(transport.responseText);
55                                 } });
56
57                 } catch (e) {
58                         console.error("Exception while trying to log the error.", e);
59                 }
60
61                 var content = "<div class='fatalError'><p>" + msg + "</p>";
62
63                 if (e.stack) {
64                         content += "<div><b>Stack trace:</b></div>" +
65                                 "<textarea name=\"stack\" readonly=\"1\">" + e.stack + "</textarea>";
66                 }
67
68                 content += "</div>";
69
70                 content += "<div class='dlgButtons'>";
71
72                 content += "<button dojoType=\"dijit.form.Button\" "+
73                                 "onclick=\"dijit.byId('exceptionDlg').hide()\">" +
74                                 __('Close') + "</button>";
75                 content += "</div>";
76
77                 if (dijit.byId("exceptionDlg"))
78                         dijit.byId("exceptionDlg").destroyRecursive();
79
80                 var dialog = new dijit.Dialog({
81                         id: "exceptionDlg",
82                         title: "Unhandled exception",
83                         style: "width: 600px",
84                         content: content});
85
86                 dialog.show();
87
88         } catch (ei) {
89                 console.error("Exception while trying to report an exception:", ei);
90                 console.error("Original exception:", e);
91
92                 alert("Exception occured while trying to report an exception.\n" +
93                         ei.stack + "\n\nOriginal exception:\n" + e.stack);
94         }
95
96 }
97
98 function param_escape(arg) {
99         if (typeof encodeURIComponent != 'undefined')
100                 return encodeURIComponent(arg);
101         else
102                 return escape(arg);
103 }
104
105 function param_unescape(arg) {
106         if (typeof decodeURIComponent != 'undefined')
107                 return decodeURIComponent(arg);
108         else
109                 return unescape(arg);
110 }
111
112 function notify_real(msg, no_hide, n_type) {
113
114         var n = $("notify");
115
116         if (!n) return;
117
118         if (notify_hide_timerid) {
119                 window.clearTimeout(notify_hide_timerid);
120         }
121
122         if (msg == "") {
123                 if (n.hasClassName("visible")) {
124                         notify_hide_timerid = window.setTimeout(function() {
125                                 n.removeClassName("visible") }, 0);
126                 }
127                 return;
128         }
129
130         /* types:
131
132                 1 - generic
133                 2 - progress
134                 3 - error
135                 4 - info
136
137         */
138
139         msg = "<span class=\"msg\"> " + __(msg) + "</span>";
140
141         if (n_type == 2) {
142                 msg = "<span><img src=\""+getInitParam("icon_indicator_white")+"\"></span>" + msg;
143                 no_hide = true;
144         } else if (n_type == 3) {
145                 msg = "<span><img src=\""+getInitParam("icon_alert")+"\"></span>" + msg;
146         } else if (n_type == 4) {
147                 msg = "<span><img src=\""+getInitParam("icon_information")+"\"></span>" + msg;
148         }
149
150         msg += " <span><img src=\""+getInitParam("icon_cross")+"\" class=\"close\" title=\"" +
151                 __("Click to close") + "\" onclick=\"notify('')\"></span>";
152
153         n.innerHTML = msg;
154
155         window.setTimeout(function() {
156                 // goddamnit firefox
157                 if (n_type == 2) {
158                 n.className = "notify notify_progress visible";
159                         } else if (n_type == 3) {
160                         n.className = "notify notify_error visible";
161                         msg = "<span><img src='images/alert.png'></span>" + msg;
162                 } else if (n_type == 4) {
163                         n.className = "notify notify_info visible";
164                 } else {
165                         n.className = "notify visible";
166                 }
167
168                 if (!no_hide) {
169                         notify_hide_timerid = window.setTimeout(function() {
170                                 n.removeClassName("visible") }, 5*1000);
171                 }
172
173         }, 10);
174
175 }
176
177 function notify(msg, no_hide) {
178         notify_real(msg, no_hide, 1);
179 }
180
181 function notify_progress(msg, no_hide) {
182         notify_real(msg, no_hide, 2);
183 }
184
185 function notify_error(msg, no_hide) {
186         notify_real(msg, no_hide, 3);
187
188 }
189
190 function notify_info(msg, no_hide) {
191         notify_real(msg, no_hide, 4);
192 }
193
194 function setCookie(name, value, lifetime, path, domain, secure) {
195
196         var d = false;
197
198         if (lifetime) {
199                 d = new Date();
200                 d.setTime(d.getTime() + (lifetime * 1000));
201         }
202
203         console.log("setCookie: " + name + " => " + value + ": " + d);
204
205         int_setCookie(name, value, d, path, domain, secure);
206
207 }
208
209 function int_setCookie(name, value, expires, path, domain, secure) {
210         document.cookie= name + "=" + escape(value) +
211                 ((expires) ? "; expires=" + expires.toGMTString() : "") +
212                 ((path) ? "; path=" + path : "") +
213                 ((domain) ? "; domain=" + domain : "") +
214                 ((secure) ? "; secure" : "");
215 }
216
217 function delCookie(name, path, domain) {
218         if (getCookie(name)) {
219                 document.cookie = name + "=" +
220                 ((path) ? ";path=" + path : "") +
221                 ((domain) ? ";domain=" + domain : "" ) +
222                 ";expires=Thu, 01-Jan-1970 00:00:01 GMT";
223         }
224 }
225
226
227 function getCookie(name) {
228
229         var dc = document.cookie;
230         var prefix = name + "=";
231         var begin = dc.indexOf("; " + prefix);
232         if (begin == -1) {
233             begin = dc.indexOf(prefix);
234             if (begin != 0) return null;
235         }
236         else {
237             begin += 2;
238         }
239         var end = document.cookie.indexOf(";", begin);
240         if (end == -1) {
241             end = dc.length;
242         }
243         return unescape(dc.substring(begin + prefix.length, end));
244 }
245
246 function gotoPreferences() {
247         document.location.href = "prefs.php";
248 }
249
250 function gotoLogout() {
251         document.location.href = "backend.php?op=logout";
252 }
253
254 function gotoMain() {
255         document.location.href = "index.php";
256 }
257
258 /** * @(#)isNumeric.js * * Copyright (c) 2000 by Sundar Dorai-Raj
259   * * @author Sundar Dorai-Raj
260   * * Email: sdoraira@vt.edu
261   * * This program is free software; you can redistribute it and/or
262   * * modify it under the terms of the GNU General Public License
263   * * as published by the Free Software Foundation; either version 2
264   * * of the License, or (at your option) any later version,
265   * * provided that any use properly credits the author.
266   * * This program is distributed in the hope that it will be useful,
267   * * but WITHOUT ANY WARRANTY; without even the implied warranty of
268   * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
269   * * GNU General Public License for more details at http://www.gnu.org * * */
270
271   var numbers=".0123456789";
272   function isNumeric(x) {
273     // is x a String or a character?
274     if(x.length>1) {
275       // remove negative sign
276       x=Math.abs(x)+"";
277       for(var j=0;j<x.length;j++) {
278         // call isNumeric recursively for each character
279         number=isNumeric(x.substring(j,j+1));
280         if(!number) return number;
281       }
282       return number;
283     }
284     else {
285       // if x is number return true
286       if(numbers.indexOf(x)>=0) return true;
287       return false;
288     }
289   }
290
291
292 function toggleSelectRowById(sender, id) {
293         var row = $(id);
294         return toggleSelectRow(sender, row);
295 }
296
297 function toggleSelectListRow(sender) {
298         var row = sender.parentNode;
299         return toggleSelectRow(sender, row);
300 }
301
302 /* this is for dijit Checkbox */
303 function toggleSelectListRow2(sender) {
304         var row = sender.domNode.parentNode;
305         return toggleSelectRow(sender, row);
306 }
307
308 /* this is for dijit Checkbox */
309 function toggleSelectRow2(sender, row, is_cdm) {
310
311         if (!row)
312                 if (!is_cdm)
313                         row = sender.domNode.parentNode.parentNode;
314                 else
315                         row = sender.domNode.parentNode.parentNode.parentNode; // oh ffs
316
317         if (sender.checked && !row.hasClassName('Selected'))
318                 row.addClassName('Selected');
319         else
320                 row.removeClassName('Selected');
321
322         if (typeof updateSelectedPrompt != undefined)
323                 updateSelectedPrompt();
324 }
325
326
327 function toggleSelectRow(sender, row) {
328
329         if (!row) row = sender.parentNode.parentNode;
330
331         if (sender.checked && !row.hasClassName('Selected'))
332                 row.addClassName('Selected');
333         else
334                 row.removeClassName('Selected');
335
336         if (typeof updateSelectedPrompt != undefined)
337                 updateSelectedPrompt();
338 }
339
340 function checkboxToggleElement(elem, id) {
341         if (elem.checked) {
342                 Effect.Appear(id, {duration : 0.5});
343         } else {
344                 Effect.Fade(id, {duration : 0.5});
345         }
346 }
347
348 function dropboxSelect(e, v) {
349         for (var i = 0; i < e.length; i++) {
350                 if (e[i].value == v) {
351                         e.selectedIndex = i;
352                         break;
353                 }
354         }
355 }
356
357 function getURLParam(param){
358         return String(window.location.href).parseQuery()[param];
359 }
360
361 function closeInfoBox(cleanup) {
362         dialog = dijit.byId("infoBox");
363
364         if (dialog)     dialog.hide();
365
366         return false;
367 }
368
369
370 function displayDlg(title, id, param, callback) {
371
372         notify_progress("Loading, please wait...", true);
373
374         var query = "?op=dlg&method=" +
375                 param_escape(id) + "&param=" + param_escape(param);
376
377         new Ajax.Request("backend.php", {
378                 parameters: query,
379                 onComplete: function (transport) {
380                         infobox_callback2(transport, title);
381                         if (callback) callback(transport);
382                 } });
383
384         return false;
385 }
386
387 function infobox_callback2(transport, title) {
388         var dialog = false;
389
390         if (dijit.byId("infoBox")) {
391                 dialog = dijit.byId("infoBox");
392         }
393
394         //console.log("infobox_callback2");
395         notify('');
396
397         var content = transport.responseText;
398
399         if (!dialog) {
400                 dialog = new dijit.Dialog({
401                         title: title,
402                         id: 'infoBox',
403                         style: "width: 600px",
404                         onCancel: function() {
405                                 return true;
406                         },
407                         onExecute: function() {
408                                 return true;
409                         },
410                         onClose: function() {
411                                 return true;
412                                 },
413                         content: content});
414         } else {
415                 dialog.attr('title', title);
416                 dialog.attr('content', content);
417         }
418
419         dialog.show();
420
421         notify("");
422 }
423
424 function getInitParam(key) {
425         return init_params[key];
426 }
427
428 function setInitParam(key, value) {
429         init_params[key] = value;
430 }
431
432 function fatalError(code, msg, ext_info) {
433         if (code == 6) {
434                 window.location.href = "index.php";
435         } else if (code == 5) {
436                 window.location.href = "public.php?op=dbupdate";
437         } else {
438
439                 if (msg == "") msg = "Unknown error";
440
441                 if (ext_info) {
442                         if (ext_info.responseText) {
443                                 ext_info = ext_info.responseText;
444                         }
445                 }
446
447                 if (ERRORS && ERRORS[code] && !msg) {
448                         msg = ERRORS[code];
449                 }
450
451                 var content = "<div><b>Error code:</b> " + code + "</div>" +
452                         "<p>" + msg + "</p>";
453
454                 if (ext_info) {
455                         content = content + "<div><b>Additional information:</b></div>" +
456                                 "<textarea style='width: 100%' readonly=\"1\">" +
457                                 ext_info + "</textarea>";
458                 }
459
460                 var dialog = new dijit.Dialog({
461                         title: "Fatal error",
462                         style: "width: 600px",
463                         content: content});
464
465                 dialog.show();
466
467         }
468
469         return false;
470
471 }
472
473 function filterDlgCheckAction(sender) {
474         var action = sender.value;
475
476         var action_param = $("filterDlg_paramBox");
477
478         if (!action_param) {
479                 console.log("filterDlgCheckAction: can't find action param box!");
480                 return;
481         }
482
483         // if selected action supports parameters, enable params field
484         if (action == 4 || action == 6 || action == 7 || action == 9) {
485                 new Effect.Appear(action_param, {duration : 0.5});
486
487                 Element.hide(dijit.byId("filterDlg_actionParam").domNode);
488                 Element.hide(dijit.byId("filterDlg_actionParamLabel").domNode);
489                 Element.hide(dijit.byId("filterDlg_actionParamPlugin").domNode);
490
491                 if (action == 7) {
492                         Element.show(dijit.byId("filterDlg_actionParamLabel").domNode);
493                 } else if (action == 9) {
494                         Element.show(dijit.byId("filterDlg_actionParamPlugin").domNode);
495                 } else {
496                         Element.show(dijit.byId("filterDlg_actionParam").domNode);
497                 }
498
499         } else {
500                 Element.hide(action_param);
501         }
502 }
503
504
505 function explainError(code) {
506         return displayDlg(__("Error explained"), "explainError", code);
507 }
508
509 function loading_set_progress(p) {
510         loading_progress += p;
511
512         if (dijit.byId("loading_bar"))
513                 dijit.byId("loading_bar").update({progress: loading_progress});
514
515         if (loading_progress >= 90)
516                 remove_splash();
517
518 }
519
520 function remove_splash() {
521
522         if (Element.visible("overlay")) {
523                 console.log("about to remove splash, OMG!");
524                 Element.hide("overlay");
525                 console.log("removed splash!");
526         }
527 }
528
529 function strip_tags(s) {
530         return s.replace(/<\/?[^>]+(>|$)/g, "");
531 }
532
533 function truncate_string(s, length) {
534         if (!length) length = 30;
535         var tmp = s.substring(0, length);
536         if (s.length > length) tmp += "&hellip;";
537         return tmp;
538 }
539
540 function hotkey_prefix_timeout() {
541
542         var date = new Date();
543         var ts = Math.round(date.getTime() / 1000);
544
545         if (hotkey_prefix_pressed && ts - hotkey_prefix_pressed >= 5) {
546                 console.log("hotkey_prefix seems to be stuck, aborting");
547                 hotkey_prefix_pressed = false;
548                 hotkey_prefix = false;
549                 Element.hide('cmdline');
550         }
551
552         setTimeout(hotkey_prefix_timeout, 1000);
553
554 }
555
556 function uploadIconHandler(rc) {
557         switch (rc) {
558                 case 0:
559                         notify_info("Upload complete.");
560                         if (inPreferences()) {
561                                 updateFeedList();
562                         } else {
563                                 setTimeout('updateFeedList(false, false)', 50);
564                         }
565                         break;
566                 case 1:
567                         notify_error("Upload failed: icon is too big.");
568                         break;
569                 case 2:
570                         notify_error("Upload failed.");
571                         break;
572         }
573 }
574
575 function removeFeedIcon(id) {
576         if (confirm(__("Remove stored feed icon?"))) {
577                 var query = "backend.php?op=pref-feeds&method=removeicon&feed_id=" + param_escape(id);
578
579                 console.log(query);
580
581                 notify_progress("Removing feed icon...", true);
582
583                 new Ajax.Request("backend.php", {
584                         parameters: query,
585                         onComplete: function(transport) {
586                                 notify_info("Feed icon removed.");
587                                 if (inPreferences()) {
588                                         updateFeedList();
589                                 } else {
590                                         setTimeout('updateFeedList(false, false)', 50);
591                                 }
592                         } });
593         }
594
595         return false;
596 }
597
598 function uploadFeedIcon() {
599         var file = $("icon_file");
600
601         if (file.value.length == 0) {
602                 alert(__("Please select an image file to upload."));
603         } else {
604                 if (confirm(__("Upload new icon for this feed?"))) {
605                         notify_progress("Uploading, please wait...", true);
606                         return true;
607                 }
608         }
609
610         return false;
611 }
612
613 function addLabel(select, callback) {
614
615         var caption = prompt(__("Please enter label caption:"), "");
616
617         if (caption != undefined) {
618
619                 if (caption == "") {
620                         alert(__("Can't create label: missing caption."));
621                         return false;
622                 }
623
624                 var query = "?op=pref-labels&method=add&caption=" +
625                         param_escape(caption);
626
627                 if (select)
628                         query += "&output=select";
629
630                 notify_progress("Loading, please wait...", true);
631
632                 if (inPreferences() && !select) active_tab = "labelConfig";
633
634                 new Ajax.Request("backend.php", {
635                         parameters: query,
636                         onComplete: function(transport) {
637                                 if (callback) {
638                                         callback(transport);
639                                 } else if (inPreferences()) {
640                                         updateLabelList();
641                                 } else {
642                                         updateFeedList();
643                                 }
644                 } });
645
646         }
647
648 }
649
650 function quickAddFeed() {
651         var query = "backend.php?op=feeds&method=quickAddFeed";
652
653         // overlapping widgets
654         if (dijit.byId("batchSubDlg")) dijit.byId("batchSubDlg").destroyRecursive();
655         if (dijit.byId("feedAddDlg"))   dijit.byId("feedAddDlg").destroyRecursive();
656
657         var dialog = new dijit.Dialog({
658                 id: "feedAddDlg",
659                 title: __("Subscribe to Feed"),
660                 style: "width: 600px",
661                 show_error: function(msg) {
662                         var elem = $("fadd_error_message");
663
664                         elem.innerHTML = msg;
665
666                         if (!Element.visible(elem))
667                                 new Effect.Appear(elem);
668
669                 },
670                 execute: function() {
671                         if (this.validate()) {
672                                 console.log(dojo.objectToQuery(this.attr('value')));
673
674                                 var feed_url = this.attr('value').feed;
675
676                                 Element.show("feed_add_spinner");
677                                 Element.hide("fadd_error_message");
678
679                                 new Ajax.Request("backend.php", {
680                                         parameters: dojo.objectToQuery(this.attr('value')),
681                                         onComplete: function(transport) {
682                                                 try {
683
684                                                         try {
685                                                                 var reply = JSON.parse(transport.responseText);
686                                                         } catch (e) {
687                                                                 Element.hide("feed_add_spinner");
688                                                                 alert(__("Failed to parse output. This can indicate server timeout and/or network issues. Backend output was logged to browser console."));
689                                                                 console.log('quickAddFeed, backend returned:' + transport.responseText);
690                                                                 return;
691                                                         }
692
693                                                         var rc = reply['result'];
694
695                                                         notify('');
696                                                         Element.hide("feed_add_spinner");
697
698                                                         console.log(rc);
699
700                                                         switch (parseInt(rc['code'])) {
701                                                         case 1:
702                                                                 dialog.hide();
703                                                                 notify_info(__("Subscribed to %s").replace("%s", feed_url));
704
705                                                                 updateFeedList();
706                                                                 break;
707                                                         case 2:
708                                                                 dialog.show_error(__("Specified URL seems to be invalid."));
709                                                                 break;
710                                                         case 3:
711                                                                 dialog.show_error(__("Specified URL doesn't seem to contain any feeds."));
712                                                                 break;
713                                                         case 4:
714                                                                 feeds = rc['feeds'];
715
716                                                                 Element.show("fadd_multiple_notify");
717
718                                                                 var select = dijit.byId("feedDlg_feedContainerSelect");
719
720                                                                 while (select.getOptions().length > 0)
721                                                                         select.removeOption(0);
722
723                                                                 select.addOption({value: '', label: __("Expand to select feed")});
724
725                                                                 var count = 0;
726                                                                 for (var feedUrl in feeds) {
727                                                                         select.addOption({value: feedUrl, label: feeds[feedUrl]});
728                                                                         count++;
729                                                                 }
730
731                                                                 Effect.Appear('feedDlg_feedsContainer', {duration : 0.5});
732
733                                                                 break;
734                                                         case 5:
735                                                                 dialog.show_error(__("Couldn't download the specified URL: %s").
736                                                                                 replace("%s", rc['message']));
737                                                                 break;
738                                                         case 6:
739                                                                 dialog.show_error(__("XML validation failed: %s").
740                                                                                 replace("%s", rc['message']));
741                                                                 break;
742                                                                 break;
743                                                         case 0:
744                                                                 dialog.show_error(__("You are already subscribed to this feed."));
745                                                                 break;
746                                                         }
747
748                                                 } catch (e) {
749                                                         console.error(transport.responseText);
750                                                         exception_error(e);
751                                                 }
752
753                                         } });
754
755                                 }
756                 },
757                 href: query});
758
759         dialog.show();
760 }
761
762 function createNewRuleElement(parentNode, replaceNode) {
763         var form = document.forms["filter_new_rule_form"];
764
765         //form.reg_exp.value = form.reg_exp.value.replace(/(<([^>]+)>)/ig,"");
766
767         var query = "backend.php?op=pref-filters&method=printrulename&rule="+
768                 param_escape(dojo.formToJson(form));
769
770         console.log(query);
771
772         new Ajax.Request("backend.php", {
773                 parameters: query,
774                 onComplete: function (transport) {
775                         try {
776                                 var li = dojo.create("li");
777
778                                 var cb = dojo.create("input", { type: "checkbox" }, li);
779
780                                 new dijit.form.CheckBox({
781                                         onChange: function() {
782                                                 toggleSelectListRow2(this) },
783                                 }, cb);
784
785                                 dojo.create("input", { type: "hidden",
786                                         name: "rule[]",
787                                         value: dojo.formToJson(form) }, li);
788
789                                 dojo.create("span", {
790                                         onclick: function() {
791                                                 dijit.byId('filterEditDlg').editRule(this);
792                                         },
793                                         innerHTML: transport.responseText }, li);
794
795                                 if (replaceNode) {
796                                         parentNode.replaceChild(li, replaceNode);
797                                 } else {
798                                         parentNode.appendChild(li);
799                                 }
800                         } catch (e) {
801                                 exception_error(e);
802                         }
803         } });
804 }
805
806 function createNewActionElement(parentNode, replaceNode) {
807         var form = document.forms["filter_new_action_form"];
808
809         if (form.action_id.value == 7) {
810                 form.action_param.value = form.action_param_label.value;
811         } else if (form.action_id.value == 9) {
812                 form.action_param.value = form.action_param_plugin.value;
813         }
814
815         var query = "backend.php?op=pref-filters&method=printactionname&action="+
816                 param_escape(dojo.formToJson(form));
817
818         console.log(query);
819
820         new Ajax.Request("backend.php", {
821                 parameters: query,
822                 onComplete: function (transport) {
823                         try {
824                                 var li = dojo.create("li");
825
826                                 var cb = dojo.create("input", { type: "checkbox" }, li);
827
828                                 new dijit.form.CheckBox({
829                                         onChange: function() {
830                                                 toggleSelectListRow2(this) },
831                                 }, cb);
832
833                                 dojo.create("input", { type: "hidden",
834                                         name: "action[]",
835                                         value: dojo.formToJson(form) }, li);
836
837                                 dojo.create("span", {
838                                         onclick: function() {
839                                                 dijit.byId('filterEditDlg').editAction(this);
840                                         },
841                                         innerHTML: transport.responseText }, li);
842
843                                 if (replaceNode) {
844                                         parentNode.replaceChild(li, replaceNode);
845                                 } else {
846                                         parentNode.appendChild(li);
847                                 }
848
849                         } catch (e) {
850                                 exception_error(e);
851                         }
852                 } });
853 }
854
855
856 function addFilterRule(replaceNode, ruleStr) {
857         if (dijit.byId("filterNewRuleDlg"))
858                 dijit.byId("filterNewRuleDlg").destroyRecursive();
859
860         var query = "backend.php?op=pref-filters&method=newrule&rule=" +
861                 param_escape(ruleStr);
862
863         var rule_dlg = new dijit.Dialog({
864                 id: "filterNewRuleDlg",
865                 title: ruleStr ? __("Edit rule") : __("Add rule"),
866                 style: "width: 600px",
867                 execute: function() {
868                         if (this.validate()) {
869                                 createNewRuleElement($("filterDlg_Matches"), replaceNode);
870                                 this.hide();
871                         }
872                 },
873                 href: query});
874
875         rule_dlg.show();
876 }
877
878 function addFilterAction(replaceNode, actionStr) {
879         if (dijit.byId("filterNewActionDlg"))
880                 dijit.byId("filterNewActionDlg").destroyRecursive();
881
882         var query = "backend.php?op=pref-filters&method=newaction&action=" +
883                 param_escape(actionStr);
884
885         var rule_dlg = new dijit.Dialog({
886                 id: "filterNewActionDlg",
887                 title: actionStr ? __("Edit action") : __("Add action"),
888                 style: "width: 600px",
889                 execute: function() {
890                         if (this.validate()) {
891                                 createNewActionElement($("filterDlg_Actions"), replaceNode);
892                                 this.hide();
893                         }
894                 },
895                 href: query});
896
897         rule_dlg.show();
898 }
899
900 function editFilterTest(query) {
901
902         if (dijit.byId("filterTestDlg"))
903                 dijit.byId("filterTestDlg").destroyRecursive();
904
905         var test_dlg = new dijit.Dialog({
906                 id: "filterTestDlg",
907                 title: "Test Filter",
908                 style: "width: 600px",
909                 results: 0,
910                 limit: 100,
911                 max_offset: 10000,
912                 getTestResults: function(query, offset) {
913                         var updquery = query + "&offset=" + offset + "&limit=" + test_dlg.limit;
914
915                         console.log("getTestResults:" + offset);
916
917                         new Ajax.Request("backend.php", {
918                                 parameters: updquery,
919                                 onComplete: function (transport) {
920                                         try {
921                                                 var result = JSON.parse(transport.responseText);
922
923                                                 if (result && dijit.byId("filterTestDlg") && dijit.byId("filterTestDlg").open) {
924                                                         test_dlg.results += result.size();
925
926                                                         console.log("got results:" + result.size());
927
928                                                         $("prefFilterProgressMsg").innerHTML = __("Looking for articles (%d processed, %f found)...")
929                                                                 .replace("%f", test_dlg.results)
930                                                                 .replace("%d", offset);
931
932                                                         console.log(offset + " " + test_dlg.max_offset);
933
934                                                         for (var i = 0; i < result.size(); i++) {
935                                                                 var tmp = new Element("table");
936                                                                 tmp.innerHTML = result[i];
937                                                                 dojo.parser.parse(tmp);
938
939                                                                 $("prefFilterTestResultList").innerHTML += tmp.innerHTML;
940                                                         }
941
942                                                         if (test_dlg.results < 30 && offset < test_dlg.max_offset) {
943
944                                                                 // get the next batch
945                                                                 window.setTimeout(function () {
946                                                                         test_dlg.getTestResults(query, offset + test_dlg.limit);
947                                                                 }, 0);
948
949                                                         } else {
950                                                                 // all done
951
952                                                                 Element.hide("prefFilterLoadingIndicator");
953
954                                                                 if (test_dlg.results == 0) {
955                                                                         $("prefFilterTestResultList").innerHTML = "<tr><td align='center'>No recent articles matching this filter have been found.</td></tr>";
956                                                                         $("prefFilterProgressMsg").innerHTML = "Articles matching this filter:";
957                                                                 } else {
958                                                                         $("prefFilterProgressMsg").innerHTML = __("Found %d articles matching this filter:")
959                                                                                 .replace("%d", test_dlg.results);
960                                                                 }
961
962                                                         }
963
964                                                 } else if (!result) {
965                                                         console.log("getTestResults: can't parse results object");
966
967                                                         Element.hide("prefFilterLoadingIndicator");
968
969                                                         notify_error("Error while trying to get filter test results.");
970
971                                                 } else {
972                                                         console.log("getTestResults: dialog closed, bailing out.");
973                                                 }
974                                         } catch (e) {
975                                                 exception_error(e);
976                                         }
977
978                                 } });
979                 },
980                 href: query});
981
982         dojo.connect(test_dlg, "onLoad", null, function(e) {
983                 test_dlg.getTestResults(query, 0);
984         });
985
986         test_dlg.show();
987
988 }
989
990 function quickAddFilter() {
991         var query = "";
992         if (!inPreferences()) {
993                 query = "backend.php?op=pref-filters&method=newfilter&feed=" +
994                         param_escape(getActiveFeedId()) + "&is_cat=" +
995                         param_escape(activeFeedIsCat());
996         } else {
997                 query = "backend.php?op=pref-filters&method=newfilter";
998         }
999
1000         console.log(query);
1001
1002         if (dijit.byId("feedEditDlg"))
1003                 dijit.byId("feedEditDlg").destroyRecursive();
1004
1005         if (dijit.byId("filterEditDlg"))
1006                 dijit.byId("filterEditDlg").destroyRecursive();
1007
1008         dialog = new dijit.Dialog({
1009                 id: "filterEditDlg",
1010                 title: __("Create Filter"),
1011                 style: "width: 600px",
1012                 test: function() {
1013                         var query = "backend.php?" + dojo.formToQuery("filter_new_form") + "&savemode=test";
1014
1015                         editFilterTest(query);
1016                 },
1017                 selectRules: function(select) {
1018                         $$("#filterDlg_Matches input[type=checkbox]").each(function(e) {
1019                                 e.checked = select;
1020                                 if (select)
1021                                         e.parentNode.addClassName("Selected");
1022                                 else
1023                                         e.parentNode.removeClassName("Selected");
1024                         });
1025                 },
1026                 selectActions: function(select) {
1027                         $$("#filterDlg_Actions input[type=checkbox]").each(function(e) {
1028                                 e.checked = select;
1029
1030                                 if (select)
1031                                         e.parentNode.addClassName("Selected");
1032                                 else
1033                                         e.parentNode.removeClassName("Selected");
1034
1035                         });
1036                 },
1037                 editRule: function(e) {
1038                         var li = e.parentNode;
1039                         var rule = li.getElementsByTagName("INPUT")[1].value;
1040                         addFilterRule(li, rule);
1041                 },
1042                 editAction: function(e) {
1043                         var li = e.parentNode;
1044                         var action = li.getElementsByTagName("INPUT")[1].value;
1045                         addFilterAction(li, action);
1046                 },
1047                 addAction: function() { addFilterAction(); },
1048                 addRule: function() { addFilterRule(); },
1049                 deleteAction: function() {
1050                         $$("#filterDlg_Actions li.[class*=Selected]").each(function(e) { e.parentNode.removeChild(e) });
1051                 },
1052                 deleteRule: function() {
1053                         $$("#filterDlg_Matches li.[class*=Selected]").each(function(e) { e.parentNode.removeChild(e) });
1054                 },
1055                 execute: function() {
1056                         if (this.validate()) {
1057
1058                                 var query = dojo.formToQuery("filter_new_form");
1059
1060                                 console.log(query);
1061
1062                                 new Ajax.Request("backend.php", {
1063                                         parameters: query,
1064                                         onComplete: function (transport) {
1065                                                 if (inPreferences()) {
1066                                                         updateFilterList();
1067                                                 }
1068
1069                                                 dialog.hide();
1070                                 } });
1071                         }
1072                 },
1073                 href: query});
1074
1075         if (!inPreferences()) {
1076                 var selectedText = getSelectionText();
1077
1078                 var lh = dojo.connect(dialog, "onLoad", function(){
1079                         dojo.disconnect(lh);
1080
1081                         if (selectedText != "") {
1082
1083                                 var feed_id = activeFeedIsCat() ? 'CAT:' + parseInt(getActiveFeedId()) :
1084                                         getActiveFeedId();
1085
1086                                 var rule = { reg_exp: selectedText, feed_id: feed_id, filter_type: 1 };
1087
1088                                 addFilterRule(null, dojo.toJson(rule));
1089
1090                         } else {
1091
1092                                 var query = "op=rpc&method=getlinktitlebyid&id=" + getActiveArticleId();
1093
1094                                 new Ajax.Request("backend.php", {
1095                                 parameters: query,
1096                                 onComplete: function(transport) {
1097                                         var reply = JSON.parse(transport.responseText);
1098
1099                                         var title = false;
1100
1101                                         if (reply && reply) title = reply.title;
1102
1103                                         if (title || getActiveFeedId() || activeFeedIsCat()) {
1104
1105                                                 console.log(title + " " + getActiveFeedId());
1106
1107                                                 var feed_id = activeFeedIsCat() ? 'CAT:' + parseInt(getActiveFeedId()) :
1108                                                         getActiveFeedId();
1109
1110                                                 var rule = { reg_exp: title, feed_id: feed_id, filter_type: 1 };
1111
1112                                                 addFilterRule(null, dojo.toJson(rule));
1113                                         }
1114
1115                                 } });
1116
1117                         }
1118
1119                 });
1120         }
1121
1122         dialog.show();
1123
1124 }
1125
1126 function resetPubSub(feed_id, title) {
1127
1128         var msg = __("Reset subscription? Tiny Tiny RSS will try to subscribe to the notification hub again on next feed update.").replace("%s", title);
1129
1130         if (title == undefined || confirm(msg)) {
1131                 notify_progress("Loading, please wait...");
1132
1133                 var query = "?op=pref-feeds&quiet=1&method=resetPubSub&ids=" + feed_id;
1134
1135                 new Ajax.Request("backend.php", {
1136                         parameters: query,
1137                         onComplete: function(transport) {
1138                                 dijit.byId("pubsubReset_Btn").attr('disabled', true);
1139                                 notify_info("Subscription reset.");
1140                         } });
1141         }
1142
1143         return false;
1144 }
1145
1146
1147 function unsubscribeFeed(feed_id, title) {
1148
1149         var msg = __("Unsubscribe from %s?").replace("%s", title);
1150
1151         if (title == undefined || confirm(msg)) {
1152                 notify_progress("Removing feed...");
1153
1154                 var query = "?op=pref-feeds&quiet=1&method=remove&ids=" + feed_id;
1155
1156                 new Ajax.Request("backend.php", {
1157                         parameters: query,
1158                         onComplete: function(transport) {
1159
1160                                         if (dijit.byId("feedEditDlg")) dijit.byId("feedEditDlg").hide();
1161
1162                                         if (inPreferences()) {
1163                                                 updateFeedList();
1164                                         } else {
1165                                                 if (feed_id == getActiveFeedId())
1166                                                         setTimeout(function() { viewfeed({feed:-5}) }, 100);
1167
1168                                                 if (feed_id < 0) updateFeedList();
1169                                         }
1170
1171                                 } });
1172         }
1173
1174         return false;
1175 }
1176
1177
1178 function backend_sanity_check_callback(transport) {
1179
1180         if (sanity_check_done) {
1181                 fatalError(11, "Sanity check request received twice. This can indicate "+
1182                   "presence of Firebug or some other disrupting extension. "+
1183                         "Please disable it and try again.");
1184                 return;
1185         }
1186
1187         var reply = JSON.parse(transport.responseText);
1188
1189         if (!reply) {
1190                 fatalError(3, "Sanity check: invalid RPC reply", transport.responseText);
1191                 return;
1192         }
1193
1194         var error_code = reply['error']['code'];
1195
1196         if (error_code && error_code != 0) {
1197                 return fatalError(error_code, reply['error']['message']);
1198         }
1199
1200         console.log("sanity check ok");
1201
1202         var params = reply['init-params'];
1203
1204         if (params) {
1205                 console.log('reading init-params...');
1206
1207                 for (k in params) {
1208                         console.log("IP: " + k + " => " + JSON.stringify(params[k]));
1209                         if (k == "label_base_index") _label_base_index = parseInt(params[k]);
1210                 }
1211
1212                 init_params = params;
1213
1214                 // PluginHost might not be available on non-index pages
1215                 window.PluginHost && PluginHost.run(PluginHost.HOOK_PARAMS_LOADED, init_params);
1216         }
1217
1218         sanity_check_done = true;
1219
1220         init_second_stage();
1221
1222 }
1223
1224 function quickAddCat(elem) {
1225         var cat = prompt(__("Please enter category title:"));
1226
1227         if (cat) {
1228
1229                 var query = "?op=rpc&method=quickAddCat&cat=" + param_escape(cat);
1230
1231                 notify_progress("Loading, please wait...", true);
1232
1233                 new Ajax.Request("backend.php", {
1234                         parameters: query,
1235                         onComplete: function (transport) {
1236                                 var response = transport.responseXML;
1237                                 var select = response.getElementsByTagName("select")[0];
1238                                 var options = select.getElementsByTagName("option");
1239
1240                                 dropbox_replace_options(elem, options);
1241
1242                                 notify('');
1243
1244                 } });
1245
1246         }
1247 }
1248
1249 function genUrlChangeKey(feed, is_cat) {
1250         var ok = confirm(__("Generate new syndication address for this feed?"));
1251
1252         if (ok) {
1253
1254                 notify_progress("Trying to change address...", true);
1255
1256                 var query = "?op=pref-feeds&method=regenFeedKey&id=" + param_escape(feed) +
1257                         "&is_cat=" + param_escape(is_cat);
1258
1259                 new Ajax.Request("backend.php", {
1260                         parameters: query,
1261                         onComplete: function(transport) {
1262                                         var reply = JSON.parse(transport.responseText);
1263                                         var new_link = reply.link;
1264
1265                                         var e = $('gen_feed_url');
1266
1267                                         if (new_link) {
1268
1269                                                 e.innerHTML = e.innerHTML.replace(/\&amp;key=.*$/,
1270                                                         "&amp;key=" + new_link);
1271
1272                                                 e.href = e.href.replace(/\&key=.*$/,
1273                                                         "&key=" + new_link);
1274
1275                                                 new Effect.Highlight(e);
1276
1277                                                 notify('');
1278
1279                                         } else {
1280                                                 notify_error("Could not change feed URL.");
1281                                         }
1282                         } });
1283         }
1284         return false;
1285 }
1286
1287 function dropbox_replace_options(elem, options) {
1288         while (elem.hasChildNodes())
1289                 elem.removeChild(elem.firstChild);
1290
1291         var sel_idx = -1;
1292
1293         for (var i = 0; i < options.length; i++) {
1294                 var text = options[i].firstChild.nodeValue;
1295                 var value = options[i].getAttribute("value");
1296
1297                 if (value == undefined) value = text;
1298
1299                 var issel = options[i].getAttribute("selected") == "1";
1300
1301                 var option = new Option(text, value, issel);
1302
1303                 if (options[i].getAttribute("disabled"))
1304                         option.setAttribute("disabled", true);
1305
1306                 elem.insert(option);
1307
1308                 if (issel) sel_idx = i;
1309         }
1310
1311         // Chrome doesn't seem to just select stuff when you pass new Option(x, y, true)
1312         if (sel_idx >= 0) elem.selectedIndex = sel_idx;
1313 }
1314
1315 // mode = all, none, invert
1316 function selectTableRows(id, mode) {
1317         var rows = $(id).rows;
1318
1319         for (var i = 0; i < rows.length; i++) {
1320                 var row = rows[i];
1321                 var cb = false;
1322                 var dcb = false;
1323
1324                 if (row.id && row.className) {
1325                         var bare_id = row.id.replace(/^[A-Z]*?-/, "");
1326                         var inputs = rows[i].getElementsByTagName("input");
1327
1328                         for (var j = 0; j < inputs.length; j++) {
1329                                 var input = inputs[j];
1330
1331                                 if (input.getAttribute("type") == "checkbox" &&
1332                                                 input.id.match(bare_id)) {
1333
1334                                         cb = input;
1335                                         dcb = dijit.getEnclosingWidget(cb);
1336                                         break;
1337                                 }
1338                         }
1339
1340                         if (cb || dcb) {
1341                                 var issel = row.hasClassName("Selected");
1342
1343                                 if (mode == "all" && !issel) {
1344                                         row.addClassName("Selected");
1345                                         cb.checked = true;
1346                                         if (dcb) dcb.set("checked", true);
1347                                 } else if (mode == "none" && issel) {
1348                                         row.removeClassName("Selected");
1349                                         cb.checked = false;
1350                                         if (dcb) dcb.set("checked", false);
1351
1352                                 } else if (mode == "invert") {
1353
1354                                         if (issel) {
1355                                                 row.removeClassName("Selected");
1356                                                 cb.checked = false;
1357                                                 if (dcb) dcb.set("checked", false);
1358                                         } else {
1359                                                 row.addClassName("Selected");
1360                                                 cb.checked = true;
1361                                                 if (dcb) dcb.set("checked", true);
1362                                         }
1363                                 }
1364                         }
1365                 }
1366         }
1367
1368 }
1369
1370 function getSelectedTableRowIds(id) {
1371         var rows = [];
1372
1373         var elem_rows = $(id).rows;
1374
1375         for (var i = 0; i < elem_rows.length; i++) {
1376                 if (elem_rows[i].hasClassName("Selected")) {
1377                         var bare_id = elem_rows[i].id.replace(/^[A-Z]*?-/, "");
1378                         rows.push(bare_id);
1379                 }
1380         }
1381
1382         return rows;
1383 }
1384
1385 function editFeed(feed, event) {
1386         if (feed <= 0)
1387                 return alert(__("You can't edit this kind of feed."));
1388
1389         var query = "backend.php?op=pref-feeds&method=editfeed&id=" +
1390                 param_escape(feed);
1391
1392         console.log(query);
1393
1394         if (dijit.byId("filterEditDlg"))
1395                 dijit.byId("filterEditDlg").destroyRecursive();
1396
1397         if (dijit.byId("feedEditDlg"))
1398                 dijit.byId("feedEditDlg").destroyRecursive();
1399
1400         dialog = new dijit.Dialog({
1401                 id: "feedEditDlg",
1402                 title: __("Edit Feed"),
1403                 style: "width: 600px",
1404                 execute: function() {
1405                         if (this.validate()) {
1406 //                                      console.log(dojo.objectToQuery(this.attr('value')));
1407
1408                                 notify_progress("Saving data...", true);
1409
1410                                 new Ajax.Request("backend.php", {
1411                                         parameters: dojo.objectToQuery(dialog.attr('value')),
1412                                         onComplete: function(transport) {
1413                                                 dialog.hide();
1414                                                 notify('');
1415                                                 updateFeedList();
1416                                 }});
1417                         }
1418                 },
1419                 href: query});
1420
1421         dialog.show();
1422 }
1423
1424 function feedBrowser() {
1425         var query = "backend.php?op=feeds&method=feedBrowser";
1426
1427         if (dijit.byId("feedAddDlg"))
1428                 dijit.byId("feedAddDlg").hide();
1429
1430         if (dijit.byId("feedBrowserDlg"))
1431                 dijit.byId("feedBrowserDlg").destroyRecursive();
1432
1433         var dialog = new dijit.Dialog({
1434                 id: "feedBrowserDlg",
1435                 title: __("More Feeds"),
1436                 style: "width: 600px",
1437                 getSelectedFeedIds: function () {
1438                         var list = $$("#browseFeedList li[id*=FBROW]");
1439                         var selected = new Array();
1440
1441                         list.each(function (child) {
1442                                 var id = child.id.replace("FBROW-", "");
1443
1444                                 if (child.hasClassName('Selected')) {
1445                                         selected.push(id);
1446                                 }
1447                         });
1448
1449                         return selected;
1450                 },
1451                 getSelectedFeeds: function () {
1452                         var list = $$("#browseFeedList li.Selected");
1453                         var selected = new Array();
1454
1455                         list.each(function (child) {
1456                                 var title = child.getElementsBySelector("span.fb_feedTitle")[0].innerHTML;
1457                                 var url = child.getElementsBySelector("a.fb_feedUrl")[0].href;
1458
1459                                 selected.push([title, url]);
1460
1461                         });
1462
1463                         return selected;
1464                 },
1465
1466                 subscribe: function () {
1467                         var mode = this.attr('value').mode;
1468                         var selected = [];
1469
1470                         if (mode == "1")
1471                                 selected = this.getSelectedFeeds();
1472                         else
1473                                 selected = this.getSelectedFeedIds();
1474
1475                         if (selected.length > 0) {
1476                                 dijit.byId("feedBrowserDlg").hide();
1477
1478                                 notify_progress("Loading, please wait...", true);
1479
1480                                 // we use dojo.toJson instead of JSON.stringify because
1481                                 // it somehow escapes everything TWICE, at least in Chrome 9
1482
1483                                 var query = "?op=rpc&method=massSubscribe&payload=" +
1484                                         param_escape(dojo.toJson(selected)) + "&mode=" + param_escape(mode);
1485
1486                                 console.log(query);
1487
1488                                 new Ajax.Request("backend.php", {
1489                                         parameters: query,
1490                                         onComplete: function (transport) {
1491                                                 notify('');
1492                                                 updateFeedList();
1493                                         }
1494                                 });
1495
1496                         } else {
1497                                 alert(__("No feeds are selected."));
1498                         }
1499
1500                 },
1501                 update: function () {
1502                         var query = dojo.objectToQuery(dialog.attr('value'));
1503
1504                         Element.show('feed_browser_spinner');
1505
1506                         new Ajax.Request("backend.php", {
1507                                 parameters: query,
1508                                 onComplete: function (transport) {
1509                                         notify('');
1510
1511                                         Element.hide('feed_browser_spinner');
1512
1513                                         var c = $("browseFeedList");
1514
1515                                         var reply = JSON.parse(transport.responseText);
1516
1517                                         var r = reply['content'];
1518                                         var mode = reply['mode'];
1519
1520                                         if (c && r) {
1521                                                 c.innerHTML = r;
1522                                         }
1523
1524                                         dojo.parser.parse("browseFeedList");
1525
1526                                         if (mode == 2) {
1527                                                 Element.show(dijit.byId('feed_archive_remove').domNode);
1528                                         } else {
1529                                                 Element.hide(dijit.byId('feed_archive_remove').domNode);
1530                                         }
1531
1532                                 }
1533                         });
1534                 },
1535                 removeFromArchive: function () {
1536                         var selected = this.getSelectedFeedIds();
1537
1538                         if (selected.length > 0) {
1539
1540                                 var pr = __("Remove selected feeds from the archive? Feeds with stored articles will not be removed.");
1541
1542                                 if (confirm(pr)) {
1543                                         Element.show('feed_browser_spinner');
1544
1545                                         var query = "?op=rpc&method=remarchive&ids=" +
1546                                                 param_escape(selected.toString());
1547                                         ;
1548
1549                                         new Ajax.Request("backend.php", {
1550                                                 parameters: query,
1551                                                 onComplete: function (transport) {
1552                                                         dialog.update();
1553                                                 }
1554                                         });
1555                                 }
1556                         }
1557                 },
1558                 execute: function () {
1559                         if (this.validate()) {
1560                                 this.subscribe();
1561                         }
1562                 },
1563                 href: query
1564         });
1565
1566         dialog.show();
1567 }
1568
1569 function showFeedsWithErrors() {
1570         var query = "backend.php?op=pref-feeds&method=feedsWithErrors";
1571
1572         if (dijit.byId("errorFeedsDlg"))
1573                 dijit.byId("errorFeedsDlg").destroyRecursive();
1574
1575         dialog = new dijit.Dialog({
1576                 id: "errorFeedsDlg",
1577                 title: __("Feeds with update errors"),
1578                 style: "width: 600px",
1579                 getSelectedFeeds: function() {
1580                         return getSelectedTableRowIds("prefErrorFeedList");
1581                 },
1582                 removeSelected: function() {
1583                         var sel_rows = this.getSelectedFeeds();
1584
1585                         console.log(sel_rows);
1586
1587                         if (sel_rows.length > 0) {
1588                                 var ok = confirm(__("Remove selected feeds?"));
1589
1590                                 if (ok) {
1591                                         notify_progress("Removing selected feeds...", true);
1592
1593                                         var query = "?op=pref-feeds&method=remove&ids="+
1594                                                 param_escape(sel_rows.toString());
1595
1596                                         new Ajax.Request("backend.php", {
1597                                                 parameters: query,
1598                                                 onComplete: function(transport) {
1599                                                         notify('');
1600                                                         dialog.hide();
1601                                                         updateFeedList();
1602                                                 } });
1603                                 }
1604
1605                         } else {
1606                                 alert(__("No feeds are selected."));
1607                         }
1608                 },
1609                 execute: function() {
1610                         if (this.validate()) {
1611                         }
1612                 },
1613                 href: query});
1614
1615         dialog.show();
1616 }
1617
1618 function get_timestamp() {
1619         var date = new Date();
1620         return Math.round(date.getTime() / 1000);
1621 }
1622
1623 function helpDialog(topic) {
1624         var query = "backend.php?op=backend&method=help&topic=" + param_escape(topic);
1625
1626         if (dijit.byId("helpDlg"))
1627                 dijit.byId("helpDlg").destroyRecursive();
1628
1629         dialog = new dijit.Dialog({
1630                 id: "helpDlg",
1631                 title: __("Help"),
1632                 style: "width: 600px",
1633                 href: query,
1634         });
1635
1636         dialog.show();
1637 }
1638
1639 function htmlspecialchars_decode (string, quote_style) {
1640   // http://kevin.vanzonneveld.net
1641   // +   original by: Mirek Slugen
1642   // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
1643   // +   bugfixed by: Mateusz "loonquawl" Zalega
1644   // +      input by: ReverseSyntax
1645   // +      input by: Slawomir Kaniecki
1646   // +      input by: Scott Cariss
1647   // +      input by: Francois
1648   // +   bugfixed by: Onno Marsman
1649   // +    revised by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
1650   // +   bugfixed by: Brett Zamir (http://brett-zamir.me)
1651   // +      input by: Ratheous
1652   // +      input by: Mailfaker (http://www.weedem.fr/)
1653   // +      reimplemented by: Brett Zamir (http://brett-zamir.me)
1654   // +    bugfixed by: Brett Zamir (http://brett-zamir.me)
1655   // *     example 1: htmlspecialchars_decode("<p>this -&gt; &quot;</p>", 'ENT_NOQUOTES');
1656   // *     returns 1: '<p>this -> &quot;</p>'
1657   // *     example 2: htmlspecialchars_decode("&amp;quot;");
1658   // *     returns 2: '&quot;'
1659   var optTemp = 0,
1660     i = 0,
1661     noquotes = false;
1662   if (typeof quote_style === 'undefined') {
1663     quote_style = 2;
1664   }
1665   string = string.toString().replace(/&lt;/g, '<').replace(/&gt;/g, '>');
1666   var OPTS = {
1667     'ENT_NOQUOTES': 0,
1668     'ENT_HTML_QUOTE_SINGLE': 1,
1669     'ENT_HTML_QUOTE_DOUBLE': 2,
1670     'ENT_COMPAT': 2,
1671     'ENT_QUOTES': 3,
1672     'ENT_IGNORE': 4
1673   };
1674   if (quote_style === 0) {
1675     noquotes = true;
1676   }
1677   if (typeof quote_style !== 'number') { // Allow for a single string or an array of string flags
1678     quote_style = [].concat(quote_style);
1679     for (i = 0; i < quote_style.length; i++) {
1680       // Resolve string input to bitwise e.g. 'PATHINFO_EXTENSION' becomes 4
1681       if (OPTS[quote_style[i]] === 0) {
1682         noquotes = true;
1683       } else if (OPTS[quote_style[i]]) {
1684         optTemp = optTemp | OPTS[quote_style[i]];
1685       }
1686     }
1687     quote_style = optTemp;
1688   }
1689   if (quote_style & OPTS.ENT_HTML_QUOTE_SINGLE) {
1690     string = string.replace(/&#0*39;/g, "'"); // PHP doesn't currently escape if more than one 0, but it should
1691     // string = string.replace(/&apos;|&#x0*27;/g, "'"); // This would also be useful here, but not a part of PHP
1692   }
1693   if (!noquotes) {
1694     string = string.replace(/&quot;/g, '"');
1695   }
1696   // Put this in last place to avoid escape being double-decoded
1697   string = string.replace(/&amp;/g, '&');
1698
1699   return string;
1700 }
1701
1702
1703 function label_to_feed_id(label) {
1704         return _label_base_index - 1 - Math.abs(label);
1705 }
1706
1707 function feed_to_label_id(feed) {
1708         return _label_base_index - 1 + Math.abs(feed);
1709 }
1710
1711 // http://stackoverflow.com/questions/6251937/how-to-get-selecteduser-highlighted-text-in-contenteditable-element-and-replac
1712
1713 function getSelectionText() {
1714         var text = "";
1715
1716         if (typeof window.getSelection != "undefined") {
1717                 var sel = window.getSelection();
1718                 if (sel.rangeCount) {
1719                         var container = document.createElement("div");
1720                         for (var i = 0, len = sel.rangeCount; i < len; ++i) {
1721                                 container.appendChild(sel.getRangeAt(i).cloneContents());
1722                         }
1723                         text = container.innerHTML;
1724                 }
1725         } else if (typeof document.selection != "undefined") {
1726                 if (document.selection.type == "Text") {
1727                         text = document.selection.createRange().textText;
1728                 }
1729         }
1730
1731         return text.stripTags();
1732 }
1733
1734 function openUrlPopup(url) {
1735         var w = window.open("");
1736
1737         w.opener = null;
1738         w.location = url;
1739 }
1740 function openArticlePopup(id) {
1741         var w = window.open("",
1742                 "ttrss_article_popup",
1743                 "height=900,width=900,resizable=yes,status=no,location=no,menubar=no,directories=no,scrollbars=yes,toolbar=no");
1744
1745         w.opener = null;
1746         w.location = "backend.php?op=article&method=view&mode=raw&html=1&zoom=1&id=" + id + "&csrf_token=" + getInitParam("csrf_token");
1747 }