]> git.wh0rd.org - tt-rss.git/blob - digest.js
update.php & daemon: chdir to script directory using __FILE__ magic constant
[tt-rss.git] / digest.js
1 var last_feeds = [];
2 var init_params = {};
3
4 var _active_feed_id = false;
5 var _active_feed_offset = false;
6 var _update_timeout = false;
7 var _view_update_timeout = false;
8 var _feedlist_expanded = false;
9 var _update_seq = 1;
10
11 function article_appear(article_id) {
12 try {
13 new Effect.Appear('A-' + article_id);
14 } catch (e) {
15 exception_error("article_appear", e);
16 }
17 }
18
19 function catchup_feed(feed_id, callback) {
20 try {
21
22 var fn = find_feed(last_feeds, feed_id).title;
23
24 if (confirm(__("Mark all articles in %s as read?").replace("%s", fn))) {
25
26 var is_cat = "";
27
28 if (feed_id < 0) is_cat = "true"; // KLUDGE
29
30 var query = "?op=rpc&subop=catchupFeed&feed_id=" +
31 feed_id + "&is_cat=" + is_cat;
32
33 new Ajax.Request("backend.php", {
34 parameters: query,
35 onComplete: function(transport) {
36 if (callback) callback(transport);
37
38 update();
39 } });
40 }
41
42 } catch (e) {
43 exception_error("catchup_article", e);
44 }
45 }
46
47 function get_visible_article_ids() {
48 try {
49 var elems = $("headlines-content").getElementsByTagName("LI");
50 var ids = [];
51
52 for (var i = 0; i < elems.length; i++) {
53 if (elems[i].id && elems[i].id.match("A-")) {
54 ids.push(elems[i].id.replace("A-", ""));
55 }
56 }
57
58 return ids;
59
60 } catch (e) {
61 exception_error("get_visible_article_ids", e);
62 }
63 }
64
65 function catchup_visible_articles(callback) {
66 try {
67
68 var ids = get_visible_article_ids();
69
70 if (confirm(__("Mark %d displayed articles as read?").replace("%d", ids.length))) {
71
72 var query = "?op=rpc&subop=catchupSelected" +
73 "&cmode=0&ids=" + param_escape(ids);
74
75 new Ajax.Request("backend.php", {
76 parameters: query,
77 onComplete: function(transport) {
78 if (callback) callback(transport);
79
80 viewfeed(_active_feed_id, 0);
81 } });
82
83 }
84
85 } catch (e) {
86 exception_error("catchup_visible_articles", e);
87 }
88 }
89
90 function catchup_article(article_id, callback) {
91 try {
92 var query = "?op=rpc&subop=catchupSelected" +
93 "&cmode=0&ids=" + article_id;
94
95 new Ajax.Request("backend.php", {
96 parameters: query,
97 onComplete: function(transport) {
98 if (callback) callback(transport);
99 } });
100
101 } catch (e) {
102 exception_error("catchup_article", e);
103 }
104 }
105
106 function set_selected_feed(feed_id) {
107 try {
108 var feeds = $("feeds-content").getElementsByTagName("LI");
109
110 for (var i = 0; i < feeds.length; i++) {
111 if (feeds[i].id == "F-" + feed_id)
112 feeds[i].className = "selected";
113 else
114 feeds[i].className = "";
115 }
116
117 _active_feed_id = feed_id;
118
119 } catch (e) {
120 exception_error("mark_selected_feed", e);
121 }
122 }
123
124 function zoom(elem, article_id) {
125 try {
126 //alert(elem + "/" + article_id);
127
128 elem.innerHTML = "<img src='images/indicator_tiny.gif'> " +
129 __("Loading, please wait...");
130
131 new Ajax.Request("backend.php", {
132 parameters: "?op=rpc&subop=digest-get-contents&article_id=" +
133 article_id,
134 onComplete: function(transport) {
135 fatal_error_check(transport);
136
137 var reply = JSON.parse(transport.responseText);
138
139 if (reply) {
140 var article = reply['article'];
141
142 elem.innerHTML = article.content;
143
144 new Effect.BlindDown(elem, {duration : 0.5});
145
146 elem.onclick = false;
147 elem.style.cursor = "auto";
148
149 catchup_article(article_id,
150 function() {
151 window.clearTimeout(_view_update_timeout);
152 _view_update_timeout = window.setTimeout("view_update()", 500);
153 $("A-" + article_id).className = "read";
154 });
155
156
157 } else {
158 elem.innerHTML = __("Error: unable to load article.");
159 }
160
161 } });
162
163
164 } catch (e) {
165 exception_error("zoom", e);
166 }
167 }
168
169 function load_more() {
170 try {
171 var pr = $("H-LOADING-IMG");
172
173 if (pr) Element.show(pr);
174
175 viewfeed(_active_feed_id, _active_feed_offset + 10, false, false, true,
176 function() {
177 var pr = $("H-LOADING-IMG");
178
179 if (pr) Element.hide(pr);
180 });
181 } catch (e) {
182 exception_error("load_more", e);
183 }
184 }
185
186 function update(callback) {
187 try {
188 console.log('updating feeds...');
189
190 window.clearTimeout(_update_timeout);
191
192 new Ajax.Request("backend.php", {
193 parameters: "?op=rpc&subop=digest-init",
194 onComplete: function(transport) {
195 fatal_error_check(transport);
196 parse_feeds(transport);
197 set_selected_feed(_active_feed_id);
198
199 if (callback) callback(transport);
200 } });
201
202 _update_timeout = window.setTimeout('update()', 5*1000);
203 } catch (e) {
204 exception_error("update", e);
205 }
206 }
207
208 function remove_headline_entry(article_id) {
209 try {
210 var elem = $('A-' + article_id);
211
212 if (elem) {
213 elem.parentNode.removeChild(elem);
214 }
215
216 } catch (e) {
217 exception_error("remove_headline_entry", e);
218 }
219 }
220
221 function view_update() {
222 try {
223 viewfeed(_active_feed_id, _active_feed_offset, false, true, true);
224 update();
225 } catch (e) {
226 exception_error("view_update", e);
227 }
228 }
229
230 function view(article_id, dismiss_only) {
231 try {
232 remove_headline_entry(article_id);
233
234 catchup_article(article_id,
235 function() {
236 window.clearTimeout(_view_update_timeout);
237 _view_update_timeout = window.setTimeout("view_update()", 500);
238 });
239
240 return dismiss_only != true;
241 } catch (e) {
242 exception_error("view", e);
243 }
244 }
245
246 function viewfeed(feed_id, offset, replace, no_effects, no_indicator, callback) {
247 try {
248
249 if (!feed_id) feed_id = _active_feed_id;
250
251 if (!offset) {
252 offset = 0;
253 } else {
254 offset = _active_feed_offset + offset;
255 }
256
257 if (replace == undefined) replace = (offset == 0);
258
259 _update_seq = _update_seq + 1;
260
261 var query = "backend.php?op=rpc&subop=digest-update&feed_id=" +
262 param_escape(feed_id) + "&offset=" + offset +
263 "&seq=" + _update_seq;
264
265 console.log(query);
266
267 if ($("F-" + feed_id)) {
268 var img = $("F-" + feed_id).getElementsByTagName("IMG")[0];
269
270 if (img && !no_indicator) {
271 img.setAttribute("orig_src", img.src);
272 img.src = 'images/indicator_tiny.gif';
273 }
274 }
275
276 new Ajax.Request("backend.php", {
277 parameters: query,
278 onComplete: function(transport) {
279 Element.hide("overlay");
280
281 fatal_error_check(transport);
282 parse_headlines(transport, replace, no_effects);
283 set_selected_feed(feed_id);
284 _active_feed_offset = offset;
285
286 if (img && !no_indicator)
287 img.src = img.getAttribute("orig_src");
288
289 if (callback) callback(transport);
290
291 } });
292
293 } catch (e) {
294 exception_error("view", e);
295 }
296 }
297
298 function find_article(articles, article_id) {
299 try {
300 for (var i = 0; i < articles.length; i++) {
301 if (articles[i].id == article_id)
302 return articles[i];
303 }
304
305 return false;
306
307 } catch (e) {
308 exception_error("find_article", e);
309 }
310 }
311
312 function find_feed(feeds, feed_id) {
313 try {
314 for (var i = 0; i < feeds.length; i++) {
315 if (feeds[i].id == feed_id)
316 return feeds[i];
317 }
318
319 return false;
320
321 } catch (e) {
322 exception_error("find_feed", e);
323 }
324 }
325
326 function get_feed_icon(feed) {
327 try {
328 if (feed.has_icon)
329 return getInitParam('icons_url') + "/" + feed.id + '.ico';
330
331 if (feed.id == -1)
332 return 'images/mark_set.png';
333
334 if (feed.id == -2)
335 return 'images/pub_set.png';
336
337 if (feed.id == -3)
338 return 'images/fresh.png';
339
340 if (feed.id == -4)
341 return 'images/tag.png';
342
343 if (feed.id < -10)
344 return 'images/label.png';
345
346 return 'images/blank_icon.gif';
347
348 } catch (e) {
349 exception_error("get_feed_icon", e);
350 }
351 }
352
353 function add_feed_entry(feed) {
354 try {
355 var icon_part = "";
356
357 icon_part = "<img src='" + get_feed_icon(feed) + "'/>";
358
359 var tmp_html = "<li id=\"F-"+feed.id+"\" " +
360 "onmouseover=\"feed_mi(this)\" onmouseout=\"feed_mo(this)\">" +
361 icon_part +
362 "<a href=\"#\" onclick=\"viewfeed("+feed.id+")\">" + feed.title + "</a>" +
363 "<div class='unread-ctr'>" +
364 "<img onclick=\"catchup_feed("+feed.id+")\" title=\"" +
365 __("Mark as read") +
366 "\" class=\"dismiss\" style='display : none' src=\"images/digest_checkbox.png\">" +
367 "<span class=\"unread\">" + feed.unread + "</span>" +
368 "</div>" +
369 "</li>";
370
371 $("feeds-content").innerHTML += tmp_html;
372
373 } catch (e) {
374 exception_error("add_feed_entry", e);
375 }
376 }
377
378 function add_headline_entry(article, feed, no_effects) {
379 try {
380
381 var icon_part = "";
382
383 icon_part = "<img class='icon' src='" + get_feed_icon(feed) + "'/>";
384
385 var mark_part = "";
386 var publ_part = "";
387
388 var tags_part = "";
389
390 if (article.tags.length > 0) {
391
392 tags_part = " " + __("in") + " ";
393
394 for (var i = 0; i < Math.min(5, article.tags.length); i++) {
395 tags_part += "<a href=\"#\" onclick=\"viewfeed('" +
396 article.tags[i] + "')\">" +
397 article.tags[i] + "</a>, ";
398 }
399
400 tags_part = tags_part.replace(/, $/, "");
401 tags_part = "<span class=\"tags\">" + tags_part + "</span>";
402 }
403
404 if (article.marked)
405 mark_part = "<img title='"+ __("Unstar article")+"' onclick=\"toggle_mark(this, "+article.id+")\" src='images/mark_set.png'>";
406 else
407 mark_part = "<img title='"+__("Star article")+"' onclick=\"toggle_mark(this, "+article.id+")\" src='images/mark_unset.png'>";
408
409 if (article.published)
410 publ_part = "<img title='"+__("Unpublish article")+"' onclick=\"toggle_pub(this, "+article.id+")\" src='images/pub_set.png'>";
411 else
412 publ_part = "<img title='"+__("Publish article")+"' onclick=\"toggle_pub(this, "+article.id+")\" src='images/pub_unset.png'>";
413
414 var style = "";
415
416 if (!no_effects) style = "style=\"display : none\"";
417
418 if (article.excerpt.trim() == "")
419 article.excerpt = __("Click to expand article.");
420
421 var li_class = "unread";
422
423 var fresh_max = getInitParam("fresh_article_max_age") * 60 * 60;
424 var d = new Date();
425
426 if (d.getTime() / 1000 - article.updated < fresh_max)
427 li_class = "fresh";
428
429 var tmp_html = "<li id=\"A-"+article.id+"\" "+style+" class=\""+li_class+"\">" +
430 icon_part +
431
432 "<div class='digest-check'>" +
433 mark_part +
434 publ_part +
435 "<img title='" + __("Share on Twitter") + "' onclick=\"tweet_article("+article.id+", true)\" src='images/art-tweet.png'>" +
436 "<img title='" + __("Mark as read") + "' onclick=\"view("+article.id+", true)\" src='images/digest_checkbox.png'>" +
437 "</div>" +
438 "<a target=\"_blank\" href=\""+article.link+"\""+
439 "onclick=\"return view("+article.id+")\" class='title'>" +
440 article.title + "</a>" +
441 "<div class='body'>" +
442 "<div title=\""+__("Click to expand article")+"\" onclick=\"zoom(this, "+article.id+")\" class='excerpt'>" +
443 article.excerpt + "</div>" +
444 "<div class='info'><a href=\#\" onclick=\"viewfeed("+feed.id+")\">" +
445 feed.title + "</a> " + tags_part + " @ " +
446 new Date(article.updated * 1000) + "</div>" +
447 "</div></li>";
448
449 $("headlines-content").innerHTML += tmp_html;
450
451 if (!no_effects)
452 window.setTimeout('article_appear(' + article.id + ')', 100);
453
454 } catch (e) {
455 exception_error("add_headline_entry", e);
456 }
457 }
458
459 function expand_feeds() {
460 try {
461 _feedlist_expanded = true;
462
463 redraw_feedlist(last_feeds);
464
465 } catch (e) {
466 exception_error("expand_feeds", e);
467 }
468 }
469
470 function redraw_feedlist(feeds) {
471 try {
472
473 $('feeds-content').innerHTML = "";
474
475 var limit = 10;
476
477 if (_feedlist_expanded) limit = feeds.length;
478
479 for (var i = 0; i < Math.min(limit, feeds.length); i++) {
480 add_feed_entry(feeds[i]);
481 }
482
483 if (feeds.length > limit) {
484 $('feeds-content').innerHTML += "<li id='F-MORE-PROMPT'>" +
485 "<img src='images/blank_icon.gif'>" +
486 "<a href=\"#\" onclick=\"expand_feeds()\">" +
487 __("%d more...").replace("%d", feeds.length-10) +
488 "</a>" + "</li>";
489 }
490
491 if (feeds.length == 0) {
492 $('feeds-content').innerHTML =
493 "<div class='insensitive' style='text-align : center'>" +
494 __("No unread feeds.") + "</div>";
495 }
496
497 } catch (e) {
498 exception_error("redraw_feedlist", e);
499 }
500 }
501
502 function parse_feeds(transport) {
503 try {
504 var reply = JSON.parse(transport.responseText);
505
506 if (!reply) return;
507
508 var feeds = reply['feeds'];
509
510 if (feeds) {
511
512 feeds.sort( function (a,b)
513 {
514 if (b.unread != a.unread)
515 return (b.unread - a.unread)
516 else
517 if (a.title > b.title)
518 return 1;
519 else if (a.title < b.title)
520 return -1;
521 else
522 return 0;
523 });
524
525 var all_articles = find_feed(feeds, -4);
526
527 update_title(all_articles.unread);
528
529 last_feeds = feeds;
530
531 redraw_feedlist(feeds);
532 }
533
534 } catch (e) {
535 exception_error("parse_feeds", e);
536 }
537 }
538
539 function parse_headlines(transport, replace, no_effects) {
540 try {
541 var reply = JSON.parse(transport.responseText);
542 if (!reply) return;
543
544 var seq = reply['seq'];
545
546 if (seq) {
547 if (seq != _update_seq) {
548 console.log("parse_headlines: wrong sequence received.");
549 return;
550 }
551 } else {
552 return;
553 }
554
555 var headlines = reply['headlines']['content'];
556 var headlines_title = reply['headlines']['title'];
557
558 if (headlines && headlines_title) {
559 $("headlines-title").innerHTML = headlines_title
560
561 if (replace) {
562 $('headlines-content').innerHTML = '';
563 Element.hide('headlines-content');
564 }
565
566 var pr = $('H-MORE-PROMPT');
567
568 if (pr) pr.parentNode.removeChild(pr);
569
570 var inserted = false;
571
572 for (var i = 0; i < headlines.length; i++) {
573
574 if (!$('A-' + headlines[i].id)) {
575 add_headline_entry(headlines[i],
576 find_feed(last_feeds, headlines[i].feed_id), !no_effects);
577
578 inserted = $("A-" + headlines[i].id);
579 }
580 }
581
582 var ids = get_visible_article_ids();
583
584 if (ids.length > 0) {
585 if (pr) {
586 $('headlines-content').appendChild(pr);
587 if (!no_effects && inserted) new Effect.ScrollTo(inserted);
588 } else {
589 $('headlines-content').innerHTML += "<li id='H-MORE-PROMPT'>" +
590 "<div class='body'>" +
591 "<a href=\"#\" onclick=\"catchup_visible_articles()\">" +
592 __("Mark as read") + "</a> | " +
593 "<a href=\"#\" onclick=\"load_more()\">" +
594 __("Load more...") + "</a>" +
595 "<img style=\"display : none\" "+
596 "id=\"H-LOADING-IMG\" src='images/indicator_tiny.gif'>" +
597 "</div></li>";
598 }
599 } else {
600 // FIXME : display some kind of "nothing to see here" prompt here
601 }
602
603 if (replace && !no_effects)
604 new Effect.Appear('headlines-content', {duration : 0.3});
605
606 //new Effect.Appear('headlines-content');
607 }
608
609 } catch (e) {
610 exception_error("parse_headlines", e);
611 }
612 }
613
614 function init_second_stage() {
615 try {
616 new Ajax.Request("backend.php", {
617 parameters: "backend.php?op=rpc&subop=digest-init",
618 onComplete: function(transport) {
619 parse_feeds(transport);
620 window.setTimeout('viewfeed(-4)', 100);
621 _update_timeout = window.setTimeout('update()', 5*1000);
622 } });
623
624 } catch (e) {
625 exception_error("init_second_stage", e);
626 }
627 }
628
629 function init() {
630 try {
631 dojo.require("dijit.Dialog");
632
633 new Ajax.Request("backend.php", {
634 parameters: "?op=rpc&subop=sanityCheck",
635 onComplete: function(transport) {
636 backend_sanity_check_callback(transport);
637 } });
638
639 } catch (e) {
640 exception_error("digest_init", e);
641 }
642 }
643
644 function toggle_mark(img, id) {
645
646 try {
647
648 var query = "?op=rpc&id=" + id + "&subop=mark";
649
650 if (!img) return;
651
652 if (img.src.match("mark_unset")) {
653 img.src = img.src.replace("mark_unset", "mark_set");
654 img.alt = __("Unstar article");
655 query = query + "&mark=1";
656 } else {
657 img.src = img.src.replace("mark_set", "mark_unset");
658 img.alt = __("Star article");
659 query = query + "&mark=0";
660 }
661
662 new Ajax.Request("backend.php", {
663 parameters: query,
664 onComplete: function(transport) {
665 update();
666 } });
667
668 } catch (e) {
669 exception_error("toggle_mark", e);
670 }
671 }
672
673 function toggle_pub(img, id, note) {
674
675 try {
676
677 var query = "?op=rpc&id=" + id + "&subop=publ";
678
679 if (note != undefined) {
680 query = query + "&note=" + param_escape(note);
681 } else {
682 query = query + "&note=undefined";
683 }
684
685 if (!img) return;
686
687 if (img.src.match("pub_unset") || note != undefined) {
688 img.src = img.src.replace("pub_unset", "pub_set");
689 img.alt = __("Unpublish article");
690 query = query + "&pub=1";
691
692 } else {
693 img.src = img.src.replace("pub_set", "pub_unset");
694 img.alt = __("Publish article");
695 query = query + "&pub=0";
696 }
697
698 new Ajax.Request("backend.php", {
699 parameters: query,
700 onComplete: function(transport) {
701 update();
702 } });
703
704 } catch (e) {
705 exception_error("toggle_pub", e);
706 }
707 }
708
709 function fatal_error(code, msg) {
710 try {
711
712 if (code == 6) {
713 window.location.href = "digest.php";
714 } else if (code == 5) {
715 window.location.href = "db-updater.php";
716 } else {
717
718 if (msg == "") msg = "Unknown error";
719
720 console.error("Fatal error: " + code + "\n" +
721 msg);
722
723 }
724
725 } catch (e) {
726 exception_error("fatalError", e);
727 }
728 }
729
730 function fatal_error_check(transport) {
731 try {
732 if (transport.responseXML) {
733 var error = transport.responseXML.getElementsByTagName("error")[0];
734
735 if (error) {
736 var code = error.getAttribute("error-code");
737 var msg = error.getAttribute("error-msg");
738 if (code != 0) {
739 fatal_error(code, msg);
740 return false;
741 }
742 }
743 }
744 } catch (e) {
745 exception_error("fatal_error_check", e);
746 }
747 return true;
748 }
749
750 function feed_mi(elem) {
751 try {
752 var imgs = elem.getElementsByTagName('IMG');
753 var spans = elem.getElementsByTagName('SPAN');
754
755 for (var i = 0; i < imgs.length; i++) {
756 if (imgs[i].className == "dismiss")
757 Element.show(imgs[i]);
758 }
759
760 for (var i = 0; i < spans.length; i++) {
761 if (spans[i].className == "unread")
762 Element.hide(spans[i]);
763 }
764
765
766 } catch (e) {
767 exception_error("feed_mi", e);
768 }
769 }
770
771 function feed_mo(elem) {
772 try {
773 var imgs = elem.getElementsByTagName('IMG');
774 var spans = elem.getElementsByTagName('SPAN');
775
776 for (var i = 0; i < imgs.length; i++) {
777 if (imgs[i].className == "dismiss")
778 Element.hide(imgs[i]);
779 }
780
781 for (var i = 0; i < spans.length; i++) {
782 if (spans[i].className == "unread")
783 Element.show(spans[i]);
784 }
785
786 } catch (e) {
787 exception_error("feed_mo", e);
788 }
789 }
790
791 function update_title(unread) {
792 try {
793 document.title = "Tiny Tiny RSS";
794
795 if (unread > 0)
796 document.title += " (" + unread + ")";
797
798 } catch (e) {
799 exception_error("update_title", e);
800 }
801 }
802
803 function tweet_article(id) {
804 try {
805
806 var query = "?op=rpc&subop=getTweetInfo&id=" + param_escape(id);
807
808 console.log(query);
809
810 var d = new Date();
811 var ts = d.getTime();
812
813 var w = window.open('backend.php?op=loading', 'ttrss_tweet',
814 "status=0,toolbar=0,location=0,width=500,height=400,scrollbars=1,menubar=0");
815
816 new Ajax.Request("backend.php", {
817 parameters: query,
818 onComplete: function(transport) {
819 var ti = JSON.parse(transport.responseText);
820
821 var share_url = "http://twitter.com/share?_=" + ts +
822 "&text=" + param_escape(ti.title) +
823 "&url=" + param_escape(ti.link);
824
825 w.location.href = share_url;
826
827 } });
828
829 } catch (e) {
830 exception_error("tweet_article", e);
831 }
832 }