var last_feeds = []; var init_params = {}; var hotkeys_map = false; var hotkey_prefix = false; var mobile_mode = false; var _active_feed_id = false; var _update_timeout = false; var _view_update_timeout = false; var _feedlist_expanded = false; var _update_seq = 1; function article_appear(article_id) { try { new Effect.Appear('A-' + article_id); } catch (e) { exception_error("article_appear", e); } } function catchup_feed(feed_id, callback) { try { var fn = find_feed(last_feeds, feed_id).title; if (confirm(__("Mark all articles in %s as read?").replace("%s", fn))) { var is_cat = ""; if (feed_id < 0) is_cat = "true"; // KLUDGE var query = "op=rpc&method=catchupFeed&feed_id=" + feed_id + "&is_cat=" + is_cat; new Ajax.Request("backend.php", { parameters: query, onComplete: function(transport) { if (callback) callback(transport); update(); } }); } } catch (e) { exception_error("catchup_article", e); } } function get_visible_article_ids() { try { var elems = $("headlines-content").getElementsByTagName("LI"); var ids = []; for (var i = 0; i < elems.length; i++) { if (elems[i].id && elems[i].id.match("A-")) { ids.push(elems[i].id.replace("A-", "")); } } return ids; } catch (e) { exception_error("get_visible_article_ids", e); } } function catchup_visible_articles(callback) { try { var ids = get_visible_article_ids(); if (confirm(ngettext("Mark %d displayed article as read?", "Mark %d displayed articles as read?", ids.length).replace("%d", ids.length))) { var query = "op=rpc&method=catchupSelected" + "&cmode=0&ids=" + param_escape(ids); new Ajax.Request("backend.php", { parameters: query, onComplete: function(transport) { if (callback) callback(transport); viewfeed(_active_feed_id, 0); } }); } } catch (e) { exception_error("catchup_visible_articles", e); } } function catchup_article(article_id, callback) { try { var query = "op=rpc&method=catchupSelected" + "&cmode=0&ids=" + article_id; new Ajax.Request("backend.php", { parameters: query, onComplete: function(transport) { if (callback) callback(transport); } }); } catch (e) { exception_error("catchup_article", e); } } function set_selected_article(article_id) { try { $$("#headlines-content > li[id*=A-]").each(function(article) { var id = article.id.replace("A-", ""); var cb = article.getElementsByTagName("INPUT")[0]; if (id == article_id) { article.addClassName("selected"); cb.checked = true; } else { article.removeClassName("selected"); cb.checked = false; } }); } catch (e) { exception_error("set_selected_article", e); } } function set_selected_feed(feed_id) { try { var feeds = $("feeds-content").getElementsByTagName("LI"); for (var i = 0; i < feeds.length; i++) { if (feeds[i].id == "F-" + feed_id) feeds[i].className = "selected"; else feeds[i].className = ""; } _active_feed_id = feed_id; } catch (e) { exception_error("set_selected_feed", e); } } function load_more() { try { var pr = $("H-LOADING-IMG"); if (pr) Element.show(pr); var offset = $$("#headlines-content > li[id*=A-][class*=fresh],li[id*=A-][class*=unread]").length; viewfeed(false, offset, false, false, true, function() { var pr = $("H-LOADING-IMG"); if (pr) Element.hide(pr); }); } catch (e) { exception_error("load_more", e); } } function update(callback) { try { console.log('updating feeds...'); window.clearTimeout(_update_timeout); new Ajax.Request("backend.php", { parameters: "op=digest&method=digestinit", onComplete: function(transport) { fatal_error_check(transport); parse_feeds(transport); set_selected_feed(_active_feed_id); if (callback) callback(transport); } }); _update_timeout = window.setTimeout('update()', 5*1000); } catch (e) { exception_error("update", e); } } function remove_headline_entry(article_id) { try { var elem = $('A-' + article_id); if (elem) { elem.parentNode.removeChild(elem); } } catch (e) { exception_error("remove_headline_entry", e); } } function view_update() { try { viewfeed(_active_feed_id, _active_feed_offset, false, true, true); update(); } catch (e) { exception_error("view_update", e); } } function view(article_id) { try { $("content").addClassName("move"); var a = $("A-" + article_id); var h = $("headlines"); setTimeout(function() { // below or above viewport, reposition headline if (a.offsetTop > h.scrollTop + h.offsetHeight || a.offsetTop+a.offsetHeight < h.scrollTop+a.offsetHeight) h.scrollTop = a.offsetTop - (h.offsetHeight/2 - a.offsetHeight/2); }, 500); new Ajax.Request("backend.php", { parameters: "op=digest&method=digestgetcontents&article_id=" + article_id, onComplete: function(transport) { fatal_error_check(transport); var reply = JSON.parse(transport.responseText); if (reply) { var article = reply['article']; var mark_part = ""; var publ_part = ""; var tags_part = ""; if (article.tags.length > 0) { tags_part = " " + __("in") + " "; for (var i = 0; i < Math.min(5, article.tags.length); i++) { //tags_part += "<a href=\"#\" onclick=\"viewfeed('" + // article.tags[i] + "')\">" + // article.tags[i] + "</a>, "; tags_part += article.tags[i] + ", "; } tags_part = tags_part.replace(/, $/, ""); tags_part = "<span class=\"tags\">" + tags_part + "</span>"; } if (article.marked) mark_part = "<img title='"+ __("Unstar article")+"' onclick=\"toggle_mark(this, "+article.id+")\" src='images/mark_set.svg'>"; else mark_part = "<img title='"+__("Star article")+"' onclick=\"toggle_mark(this, "+article.id+")\" src='images/mark_unset.svg'>"; if (article.published) publ_part = "<img title='"+__("Unpublish article")+"' onclick=\"toggle_pub(this, "+article.id+")\" src='images/pub_set.svg'>"; else publ_part = "<img title='"+__("Publish article")+"' onclick=\"toggle_pub(this, "+article.id+")\" src='images/pub_unset.svg'>"; var tmp = "<div id=\"inner\">" + "<div id=\"ops\">" + mark_part + publ_part + "</div>" + "<h1>" + "<a target=\"_blank\" href=\""+article.url+"\">" + article.title + "</a>" + "</h1>" + "<div id=\"tags\">" + tags_part + "</div>" + article.content + "</div>"; $("article-content").innerHTML = tmp; $("article").addClassName("visible"); set_selected_article(article.id); catchup_article(article_id, function() { $("A-" + article_id).addClassName("read"); }); } else { elem.innerHTML = __("Error: unable to load article."); } } }); return false; } catch (e) { exception_error("view", e); } } function close_feed() { $("headlines").removeClassName("move"); if (mobile_mode) set_selected_feed(false); } function go_back() { if ($("article").hasClassName("visible")) { close_article(); } else { close_feed(); } } function close_article() { $("content").removeClassName("move"); $("article").removeClassName("visible"); } function viewfeed(feed_id, offset, replace, no_effects, no_indicator, callback) { try { $("headlines").addClassName("move"); if (!feed_id) feed_id = _active_feed_id; if (offset == undefined) offset = 0; if (replace == undefined) replace = (offset == 0); _update_seq = _update_seq + 1; if (!offset) $("headlines").scrollTop = 0; var query = "op=digest&method=digestupdate&feed_id=" + param_escape(feed_id) + "&offset=" + offset + "&seq=" + _update_seq; console.log(query); var img = false; if ($("F-" + feed_id)) { img = $("F-" + feed_id).getElementsByTagName("IMG")[0]; if (img && !no_indicator) { img.setAttribute("orig_src", img.src); img.src = 'images/indicator_tiny.gif'; } } new Ajax.Request("backend.php", { parameters: query, onComplete: function(transport) { Element.hide("overlay"); fatal_error_check(transport); parse_headlines(transport, replace, no_effects); set_selected_feed(feed_id); _active_feed_offset = offset; if (img && !no_indicator) img.src = img.getAttribute("orig_src"); if (callback) callback(transport); } }); } catch (e) { exception_error("view", e); } } function find_article(articles, article_id) { try { for (var i = 0; i < articles.length; i++) { if (articles[i].id == article_id) return articles[i]; } return false; } catch (e) { exception_error("find_article", e); } } function find_feed(feeds, feed_id) { try { for (var i = 0; i < feeds.length; i++) { if (feeds[i].id == feed_id) return feeds[i]; } return false; } catch (e) { exception_error("find_feed", e); } } function get_feed_icon(feed) { try { if (feed.has_icon) return getInitParam('icons_url') + "/" + feed.id + '.ico'; if (feed.id == -1) return 'images/mark_set.svg'; if (feed.id == -2) return 'images/pub_set.svg'; if (feed.id == -3) return 'images/fresh.png'; if (feed.id == -4) return 'images/tag.png'; if (feed.id < -10) return 'images/label.png'; return 'images/blank_icon.gif'; } catch (e) { exception_error("get_feed_icon", e); } } function add_feed_entry(feed) { try { var icon_part = ""; icon_part = "<img src='" + get_feed_icon(feed) + "'/>"; var title = (feed.title.length > 30) ? feed.title.substring(0, 30) + "…" : feed.title; var tmp_html = "<li id=\"F-"+feed.id+"\" onclick=\"viewfeed("+feed.id+")\">" + "<div class='unread-ctr'>" + "<span class=\"unread\">" + feed.unread + "</span></div>" + icon_part + title + "</li>"; $("feeds-content").innerHTML += tmp_html; } catch (e) { exception_error("add_feed_entry", e); } } function add_headline_entry(article, feed, no_effects) { try { var icon_part = ""; icon_part = "<img class='icon' src='" + get_feed_icon(feed) + "'/>"; var style = ""; //if (!no_effects) style = "style=\"display : none\""; if (article.excerpt.trim() == "") article.excerpt = __("Click to expand article."); var li_class = "unread"; var fresh_max = getInitParam("fresh_article_max_age") * 60 * 60; var d = new Date(); if (d.getTime() / 1000 - article.updated < fresh_max) li_class = "fresh"; var checkbox_part = "<input type=\"checkbox\" class=\"cb\" onclick=\"toggle_select_article(this)\"/>"; var date = new Date(article.updated * 1000); var date_part = date.toString().substring(0,21); var tmp_html = "<li id=\"A-"+article.id+"\" "+style+" class=\""+li_class+"\">" + checkbox_part + icon_part + "<a target=\"_blank\" href=\""+article.link+"\""+ "onclick=\"return view("+article.id+")\" class='title'>" + article.title + "</a>" + "<div class='body'>" + "<div onclick=\"view("+article.id+")\" class='excerpt'>" + article.excerpt + "</div>" + "<div onclick=\"view("+article.id+")\" class='info'>"; /* tmp_html += "<a href=\#\" onclick=\"viewfeed("+feed.id+")\">" + feed.title + "</a> " + " @ "; */ tmp_html += date_part + "</div>" + "</div></li>"; $("headlines-content").innerHTML += tmp_html; if (!no_effects) window.setTimeout('article_appear(' + article.id + ')', 100); } catch (e) { exception_error("add_headline_entry", e); } } function expand_feeds() { try { _feedlist_expanded = true; redraw_feedlist(last_feeds); } catch (e) { exception_error("expand_feeds", e); } } function redraw_feedlist(feeds) { try { $('feeds-content').innerHTML = ""; var limit = 10; if (_feedlist_expanded) limit = feeds.length; for (var i = 0; i < Math.min(limit, feeds.length); i++) { add_feed_entry(feeds[i]); } if (feeds.length > limit) { $('feeds-content').innerHTML += "<li id='F-MORE-PROMPT'>" + "<img src='images/blank_icon.gif'>" + "<a href=\"#\" onclick=\"expand_feeds()\">" + ngettext("%d more...", "%d more...", feeds.length-10).replace("%d", feeds.length-10) + "</a>" + "</li>"; } if (feeds.length == 0) { $('feeds-content').innerHTML = "<div class='insensitive' style='text-align : center'>" + __("No unread feeds.") + "</div>"; } if (_active_feed_id) set_selected_feed(_active_feed_id); } catch (e) { exception_error("redraw_feedlist", e); } } function parse_feeds(transport) { try { var reply = JSON.parse(transport.responseText); if (!reply) return; var feeds = reply['feeds']; if (feeds) { feeds.sort( function (a,b) { if (b.unread != a.unread) return (b.unread - a.unread); else if (a.title > b.title) return 1; else if (a.title < b.title) return -1; else return 0; }); var all_articles = find_feed(feeds, -4); update_title(all_articles.unread); last_feeds = feeds; redraw_feedlist(feeds); } if (reply['hotkeys']) { hotkeys_map = reply['hotkeys']; } } catch (e) { console.log(e); //exception_error("parse_feeds", e); } } function parse_headlines(transport, replace, no_effects) { try { var reply = JSON.parse(transport.responseText); if (!reply) return; var seq = reply['seq']; if (seq) { if (seq != _update_seq) { console.log("parse_headlines: wrong sequence received."); return; } } else { return; } var headlines = reply['headlines']['content']; var headlines_title = reply['headlines']['title']; if (headlines && headlines_title) { if (replace) { $('headlines-content').innerHTML = ''; } var pr = $('H-MORE-PROMPT'); if (pr) pr.parentNode.removeChild(pr); var inserted = false; for (var i = 0; i < headlines.length; i++) { if (!$('A-' + headlines[i].id)) { add_headline_entry(headlines[i], find_feed(last_feeds, headlines[i].feed_id), !no_effects); } } console.log(inserted.id); var ids = get_visible_article_ids(); if (ids.length > 0) { if (pr) { $('headlines-content').appendChild(pr); } else { $('headlines-content').innerHTML += "<li id='H-MORE-PROMPT'>" + "<div class='body'>" + "<a href=\"#\" onclick=\"catchup_visible_articles()\">" + __("Mark as read") + "</a> | " + "<a href=\"javascript:load_more()\">" + __("Load more...") + "</a>" + "<img style=\"display : none\" "+ "id=\"H-LOADING-IMG\" src='images/indicator_tiny.gif'>" + "</div></li>"; } } else { // FIXME : display some kind of "nothing to see here" prompt here } // if (replace && !no_effects) // new Effect.Appear('headlines-content', {duration : 0.3}); //new Effect.Appear('headlines-content'); } } catch (e) { exception_error("parse_headlines", e); } } function init_second_stage() { try { new Ajax.Request("backend.php", { parameters: "op=digest&method=digestinit&init=1", onComplete: function(transport) { parse_feeds(transport); Element.hide("overlay"); document.onkeydown = hotkey_handler; if (!mobile_mode) window.setTimeout('viewfeed(-4)', 100); _update_timeout = window.setTimeout('update()', 5*1000); } }); } catch (e) { exception_error("init_second_stage", e); } } function init(mobile) { try { mobile_mode = mobile; new Ajax.Request("backend.php", { parameters: {op: "rpc", method: "sanityCheck"}, onComplete: function(transport) { backend_sanity_check_callback(transport); } }); } catch (e) { exception_error("digest_init", e); } } function toggle_mark(img, id) { try { var query = "op=rpc&id=" + id + "&method=mark"; if (!img) return; if (img.src.match("mark_unset")) { img.src = img.src.replace("mark_unset", "mark_set"); img.alt = __("Unstar article"); query = query + "&mark=1"; } else { img.src = img.src.replace("mark_set", "mark_unset"); img.alt = __("Star article"); query = query + "&mark=0"; } new Ajax.Request("backend.php", { parameters: query, onComplete: function(transport) { update(); } }); } catch (e) { exception_error("toggle_mark", e); } } function toggle_pub(img, id, note) { try { var query = "op=rpc&id=" + id + "&method=publ"; if (note != undefined) { query = query + "¬e=" + param_escape(note); } else { query = query + "¬e=undefined"; } if (!img) return; if (img.src.match("pub_unset") || note != undefined) { img.src = img.src.replace("pub_unset", "pub_set"); img.alt = __("Unpublish article"); query = query + "&pub=1"; } else { img.src = img.src.replace("pub_set", "pub_unset"); img.alt = __("Publish article"); query = query + "&pub=0"; } new Ajax.Request("backend.php", { parameters: query, onComplete: function(transport) { update(); } }); } catch (e) { exception_error("toggle_pub", e); } } function fatal_error(code, msg) { try { if (code == 6) { window.location.href = "digest.php"; } else if (code == 5) { window.location.href = "public.php?op=dbupdate"; } else { if (msg == "") msg = "Unknown error"; console.error("Fatal error: " + code + "\n" + msg); } } catch (e) { exception_error("fatalError", e); } } function fatal_error_check(transport) { try { if (transport.responseXML) { var error = transport.responseXML.getElementsByTagName("error")[0]; if (error) { var code = error.getAttribute("error-code"); var msg = error.getAttribute("error-msg"); if (code != 0) { fatal_error(code, msg); return false; } } } } catch (e) { exception_error("fatal_error_check", e); } return true; } function update_title(unread) { try { document.title = "Tiny Tiny RSS"; if (unread > 0) document.title += " (" + unread + ")"; } catch (e) { exception_error("update_title", e); } } function toggle_select_article(elem) { try { var article = elem.parentNode; if (article.hasClassName("selected")) article.removeClassName("selected"); else article.addClassName("selected"); } catch (e) { exception_error("toggle_select_article", e); } } function hotkey_handler(e) { try { if (e.target.nodeName == "INPUT" || e.target.nodeName == "TEXTAREA") return; var keycode = false; var shift_key = false; var cmdline = $('cmdline'); try { shift_key = e.shiftKey; } catch (e) { } if (window.event) { keycode = window.event.keyCode; } else if (e) { keycode = e.which; } var keychar = String.fromCharCode(keycode); if (!shift_key) keychar = keychar.toLowerCase(); if (keycode == 16) return; // ignore lone shift if (keycode == 17) return; // ignore lone ctrl var hotkey = keychar.search(/[a-zA-Z0-9]/) != -1 ? keychar : "(" + keycode + ")"; hotkey = hotkey_prefix ? hotkey_prefix + " " + hotkey : hotkey; hotkey_prefix = false; var hotkey_action = false; var hotkeys = getInitParam("hotkeys"); for (sequence in hotkeys[1]) { if (sequence == hotkey) { hotkey_action = hotkeys[1][sequence]; break; } } switch (keycode) { case 27: // esc go_back(); return false; } switch (hotkey_action) { case "next_feed": var feeds = $$("#feeds li"); for (var i = 0; i < feeds.length; i++) { var base_id = feeds[i].id.replace("F-", ""); if (base_id == _active_feed_id) { if (feeds[i+1]) { viewfeed(feeds[i+1].id.replace("F-", "")); } break; } } return false; case "prev_feed": var feeds = $$("#feeds li"); for (var i = 0; i < feeds.length; i++) { var base_id = feeds[i].id.replace("F-", ""); if (base_id == _active_feed_id) { if (feeds[i-1]) { viewfeed(feeds[i-1].id.replace("F-", "")); } break; } } return false; case "next_article": return false; case "prev_article": return false; default: console.log("unhandled action: " + hotkey_action + "; hotkey: " + hotkey); } } catch (e) { exception_error("hotkey_handler", e); } }