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