summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--feedlist.js71
-rw-r--r--functions.js61
-rw-r--r--functions.php26
-rw-r--r--gears_init.js87
-rwxr-xr-ximages/offline.pngbin0 -> 1013 bytes
-rwxr-xr-ximages/online.pngbin0 -> 1019 bytes
-rw-r--r--manifest.json19
-rw-r--r--modules/backend-rpc.php206
-rw-r--r--modules/popup-dialog.php42
-rw-r--r--offline.js1421
-rw-r--r--tt-rss.css8
-rw-r--r--tt-rss.js57
-rw-r--r--tt-rss.php25
-rw-r--r--viewfeed.js218
14 files changed, 2166 insertions, 75 deletions
diff --git a/feedlist.js b/feedlist.js
index 8f1b5efec..034900619 100644
--- a/feedlist.js
+++ b/feedlist.js
@@ -30,13 +30,59 @@ function viewCategory(cat) {
return false;
}
+function printFeedEntry(id, title, row_class, unread, icon) {
+
+ var tmp = "";
+ var fctr_class = "";
+ var feed_icon = "";
+
+ if (unread > 0) {
+ row_class += "Unread";
+ fctr_class = "feedCtrHasUnread";
+ } else {
+ fctr_class = "feedCtrNoUnread";
+ }
+
+ if (icon) {
+ feed_icon = "<img id='FIMG-"+id+"' src='" + icon + "'>";
+ } else {
+ feed_icon = "<img id='FIMG-"+id+"' src='images/blank_icon.gif'>";
+ }
+
+ var link = "<a title=\"FIXME\" id=\"FEEDL-"+id+"\""+
+ "href=\"javascript:viewfeed('"+id+"', '', false, '', false, 0);\">"+
+ title + "</a>";
+
+ tmp += "<li id='FEEDR-"+id+"' class="+row_class+">" + feed_icon +
+ "<span id=\"FEEDN-"+id+"\">" + link + "</span>";
+
+ tmp += " <span class='"+fctr_class+"' id=\"FEEDCTR-"+id+"\">" +
+ "(<span id=\"FEEDU-"+id+"\">"+unread+"</span>)</span>";
+
+ tmp += "</li>";
+
+ return tmp;
+}
+
+function render_feedlist(data) {
+ try {
+
+ var f = document.getElementById("feeds-frame");
+ f.innerHTML = data;
+// cache_invalidate("FEEDLIST");
+// cache_inject("FEEDLIST", data, getInitParam("num_feeds"));
+ feedlist_init();
+
+ } catch (e) {
+ exception_error("render_feedlist", e);
+ }
+}
+
function feedlist_callback2(transport) {
try {
debug("feedlist_callback2");
if (!transport_error_check(transport)) return;
- var f = document.getElementById("feeds-frame");
- f.innerHTML = transport.responseText;
- feedlist_init();
+ render_feedlist(transport.responseText);
} catch (e) {
exception_error("feedlist_callback2", e);
}
@@ -46,19 +92,23 @@ function viewNextFeedPage() {
try {
//if (!getActiveFeedId()) return;
- debug("viewNextFeedPage: calling viewfeed(), p: " + _feed_cur_page+1);
+ debug("viewNextFeedPage: calling viewfeed(), p: " + parseInt(_feed_cur_page+1));
viewfeed(getActiveFeedId(), undefined, activeFeedIsCat(), undefined,
- undefined, _feed_cur_page+1);
+ undefined, parseInt(_feed_cur_page+1));
} catch (e) {
exception_error("viewNextFeedPage", e);
}
}
+
function viewfeed(feed, subop, is_cat, subop_param, skip_history, offset) {
try {
+ if (offline_mode) return viewfeed_offline(feed, subop, is_cat, subop_param,
+ skip_history, offset);
+
// if (!offset) page_offset = 0;
last_requested_article = 0;
@@ -136,6 +186,8 @@ function viewfeed(feed, subop, is_cat, subop_param, skip_history, offset) {
if (subop == "MarkAllRead") {
+ catchup_local_feed(feed, is_cat);
+
var show_next_feed = getInitParam("on_catchup_show_next_feed") == "1";
if (show_next_feed) {
@@ -257,6 +309,7 @@ function viewfeed(feed, subop, is_cat, subop_param, skip_history, offset) {
f.innerHTML = cache_find_param(cache_prefix + feed, unread_ctr);
request_counters();
+ remove_splash();
} else {
@@ -349,6 +402,8 @@ function toggleCollapseCat(cat) {
new Ajax.Request("backend.php?op=feeds&subop=collapse&cid=" +
param_escape(cat));
+ local_collapse_cat(cat);
+
} catch (e) {
exception_error("toggleCollapseCat", e);
}
@@ -397,7 +452,7 @@ function feedlist_init() {
document.onmousedown = mouse_down_handler;
document.onmouseup = mouse_up_handler;
- setTimeout("timeout()", 1);
+ if (!offline_mode) setTimeout("timeout()", 1);
if (typeof correctPNG != 'undefined') {
correctPNG();
@@ -605,6 +660,8 @@ function request_counters_real() {
try {
+ if (offline_mode) return;
+
debug("requesting counters...");
var query = "backend.php?op=rpc&subop=getAllCounters";
@@ -654,3 +711,5 @@ function request_counters() {
exception_error("request_counters", e);
}
}
+
+
diff --git a/functions.js b/functions.js
index 2c09be16c..ff6075f11 100644
--- a/functions.js
+++ b/functions.js
@@ -18,6 +18,8 @@ function is_opera() {
function exception_error(location, e, ext_info) {
var msg = format_exception_error(location, e);
+ if (!ext_info) ext_info = "N/A";
+
disableHotkeys();
try {
@@ -578,6 +580,12 @@ function parse_counters(reply, scheduled_call) {
debug("Subscribed feed number changed, refreshing feedlist");
setTimeout('updateFeedList(false, false)', 50);
}
+ } else {
+/* var fl = document.getElementById("feeds-frame").innerHTML;
+ if (fl) {
+ cache_invalidate("FEEDLIST");
+ cache_inject("FEEDLIST", fl, getInitParam("num_feeds"));
+ } */
}
} catch (e) {
@@ -621,6 +629,8 @@ function parse_counters_reply(transport, scheduled_call) {
function all_counters_callback2(transport, async_call) {
try {
if (async_call) async_counters_work = true;
+
+ if (offline_mode) return;
debug("<b>all_counters_callback2 IN: " + transport + "</b>");
parse_counters_reply(transport);
@@ -848,25 +858,28 @@ function hideOrShowFeedsCategory(id, hide) {
}
// debug("end cat: " + node.id + " unread " + cat_unread);
-
- if (cat_unread == 0) {
- if (cat_node.style == undefined) {
- debug("ERROR: supplied cat_node " + cat_node +
- " has no styles. WTF?");
- return;
- }
- if (hide) {
- //cat_node.style.display = "none";
- Effect.Fade(cat_node, {duration : 0.3,
- queue: { position: 'end', scope: 'CFADE-' + node.id, limit: 1 }});
+
+ if (cat_node) {
+
+ if (cat_unread == 0) {
+ if (cat_node.style == undefined) {
+ debug("ERROR: supplied cat_node " + cat_node +
+ " has no styles. WTF?");
+ return;
+ }
+ if (hide) {
+ //cat_node.style.display = "none";
+ Effect.Fade(cat_node, {duration : 0.3,
+ queue: { position: 'end', scope: 'CFADE-' + node.id, limit: 1 }});
+ } else {
+ cat_node.style.display = "list-item";
+ }
} else {
- cat_node.style.display = "list-item";
- }
- } else {
- try {
- cat_node.style.display = "list-item";
- } catch (e) {
- debug(e);
+ try {
+ cat_node.style.display = "list-item";
+ } catch (e) {
+ debug(e);
+ }
}
}
@@ -1487,6 +1500,8 @@ function storeInitParam(key, value) {
function fatalError(code, msg, ext_info) {
try {
+ if (!ext_info) ext_info = "N/A";
+
if (code == 6) {
window.location.href = "tt-rss.php";
} else if (code == 5) {
@@ -2103,3 +2118,13 @@ function transport_error_check(transport) {
return true;
}
+function strip_tags(s) {
+ return s.replace(/<\/?[^>]+(>|$)/g, "");
+}
+
+function truncate_string(s, length) {
+ if (!length) length = 30;
+ var tmp = s.substring(0, length);
+ if (s.length > length) tmp += "&hellip;";
+ return tmp;
+}
diff --git a/functions.php b/functions.php
index ff6011e8d..08e71b9d9 100644
--- a/functions.php
+++ b/functions.php
@@ -1996,6 +1996,13 @@
}
}
+ function bool_to_sql_bool($s) {
+ if ($s) {
+ return "true";
+ } else {
+ return "false";
+ }
+ }
function toggleEvenOdd($a) {
if ($a == "even")
@@ -2958,6 +2965,9 @@
print "<param key=\"hide_read_feeds\" value=\"" .
(int) get_pref($link, "HIDE_READ_FEEDS") . "\"/>";
+ print "<param key=\"enable_feed_cats\" value=\"" .
+ (int) get_pref($link, "ENABLE_FEED_CATS") . "\"/>";
+
print "<param key=\"feeds_sort_by_unread\" value=\"" .
(int) get_pref($link, "FEEDS_SORT_BY_UNREAD") . "\"/>";
@@ -3003,12 +3013,28 @@
print "<param key=\"sync_counters\" value=\"1\"/>";
+ $result = db_query($link, "SELECT COUNT(*) AS cf FROM
+ ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
+
+ $num_feeds = db_fetch_result($result, 0, "cf");
+
+ print "<param key=\"num_feeds\" value=\"".
+ (int)$num_feeds. "\"/>";
+
print "</init-params>";
}
function print_runtime_info($link) {
print "<runtime-info>";
+ $result = db_query($link, "SELECT COUNT(*) AS cf FROM
+ ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
+
+ $num_feeds = db_fetch_result($result, 0, "cf");
+
+ print "<param key=\"num_feeds\" value=\"".
+ (int)$num_feeds. "\"/>";
+
if (ENABLE_UPDATE_DAEMON) {
print "<param key=\"daemon_is_running\" value=\"".
sprintf("%d", file_is_locked("update_daemon.lock")) . "\"/>";
diff --git a/gears_init.js b/gears_init.js
new file mode 100644
index 000000000..3462d73e8
--- /dev/null
+++ b/gears_init.js
@@ -0,0 +1,87 @@
+// Copyright 2007, Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// 3. Neither the name of Google Inc. nor the names of its contributors may be
+// used to endorse or promote products derived from this software without
+// specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Sets up google.gears.*, which is *the only* supported way to access Gears.
+//
+// Circumvent this file at your own risk!
+//
+// In the future, Gears may automatically define google.gears.* without this
+// file. Gears may use these objects to transparently fix bugs and compatibility
+// issues. Applications that use the code below will continue to work seamlessly
+// when that happens.
+
+(function() {
+ // We are already defined. Hooray!
+ if (window.google && google.gears) {
+ return;
+ }
+
+ var factory = null;
+
+ // Firefox
+ if (typeof GearsFactory != 'undefined') {
+ factory = new GearsFactory();
+ } else {
+ // IE
+ try {
+ factory = new ActiveXObject('Gears.Factory');
+ // privateSetGlobalObject is only required and supported on IE Mobile on
+ // WinCE.
+ if (factory.getBuildInfo().indexOf('ie_mobile') != -1) {
+ factory.privateSetGlobalObject(this);
+ }
+ } catch (e) {
+ // Safari
+ if ((typeof navigator.mimeTypes != 'undefined')
+ && navigator.mimeTypes["application/x-googlegears"]) {
+ factory = document.createElement("object");
+ factory.style.display = "none";
+ factory.width = 0;
+ factory.height = 0;
+ factory.type = "application/x-googlegears";
+ document.documentElement.appendChild(factory);
+ }
+ }
+ }
+
+ // *Do not* define any objects if Gears is not installed. This mimics the
+ // behavior of Gears defining the objects in the future.
+ if (!factory) {
+ return;
+ }
+
+ // Now set up the objects, being careful not to overwrite anything.
+ //
+ // Note: In Internet Explorer for Windows Mobile, you can't add properties to
+ // the window object. However, global objects are automatically added as
+ // properties of the window object in all browsers.
+ if (!window.google) {
+ google = {};
+ }
+
+ if (!google.gears) {
+ google.gears = {factory: factory};
+ }
+})();
diff --git a/images/offline.png b/images/offline.png
new file mode 100755
index 000000000..7a8291776
--- /dev/null
+++ b/images/offline.png
Binary files differ
diff --git a/images/online.png b/images/online.png
new file mode 100755
index 000000000..749a416d2
--- /dev/null
+++ b/images/online.png
Binary files differ
diff --git a/manifest.json b/manifest.json
new file mode 100644
index 000000000..98794670b
--- /dev/null
+++ b/manifest.json
@@ -0,0 +1,19 @@
+{
+ "betaManifestVersion": 1,
+ "version": "1",
+ "entries": [
+ { "url": "tt-rss.php"},
+ { "url": "tt-rss.css"},
+ { "url": "viewfeed.js"},
+ { "url": "feedlist.js"},
+ { "url": "functions.js"},
+ { "url": "offline.js"},
+ { "url": "tt-rss.js"},
+ { "url": "lib/scriptaculous/effects.js"},
+ { "url": "lib/scriptaculous/controls.js"},
+ { "url": "lib/scriptaculous/dragdrop.js"},
+ { "url": "lib/scriptaculous/scriptaculous.js"},
+ { "url": "lib/prototype.js"},
+ { "url": "gears_init.js"}
+ ]
+}
diff --git a/modules/backend-rpc.php b/modules/backend-rpc.php
index 04e2858ca..6bae380a1 100644
--- a/modules/backend-rpc.php
+++ b/modules/backend-rpc.php
@@ -514,6 +514,212 @@
return;
}
+ if ($subop == "download") {
+ $stage = (int) $_REQUEST["stage"];
+ $cidt = db_escape_string($_REQUEST["cidt"]);
+ $cidb = db_escape_string($_REQUEST["cidb"]);
+ $sync = db_escape_string($_REQUEST["sync"]);
+ //$amount = (int) $_REQUEST["amount"];
+ //$unread_only = db_escape_string($_REQUEST["unread_only"]);
+ //if (!$amount) $amount = 50;
+
+ $amount = 100;
+ $unread_only = true;
+
+ print "<rpc-reply>";
+
+ $sync = split(";", $sync);
+
+ print "<sync>";
+
+ if (count($sync) > 0) {
+ if (strtotime($sync[0])) {
+ $last_online = db_escape_string($sync[0]);
+
+ print "<sync-point><![CDATA[$last_online]]></sync-point>";
+
+ for ($i = 1; $i < count($sync); $i++) {
+ $e = split(",", $sync[$i]);
+
+ if (count($e) == 3) {
+
+ $id = (int) $e[0];
+ $unread = bool_to_sql_bool((bool) $e[1]);
+ $marked = bool_to_sql_bool((bool) $e[2]);
+
+ /* Marked status is not synchronized yet */
+
+ $query = "UPDATE ttrss_user_entries SET
+ unread = $unread,
+ last_read = '$last_online'
+ WHERE ref_id = '$id' AND
+ (last_read IS NULL OR last_read < '$last_online') AND
+ owner_uid = ".$_SESSION["uid"];
+
+ $result = db_query($link, $query);
+
+// if (db_affected_rows($result) > 0) {
+ print "<sync-ok id=\"$id\"/>";
+// }
+
+ }
+ }
+
+ /* Maybe we need to further update local DB for this client */
+
+ $query = "SELECT ref_id,unread FROM ttrss_user_entries
+ WHERE last_read >= '$last_online' AND
+ owner_uid = ".$_SESSION["uid"];
+
+ $result = db_query($link, $query);
+
+ while ($line = db_fetch_assoc($result)) {
+ print "<sync-ok id=\"".$line["ref_id"]."\"/>";
+ }
+
+ }
+ }
+
+ print "</sync>";
+
+ if ($stage == 0) {
+ print "<feeds>";
+
+ $result = db_query($link, "SELECT id, title, cat_id FROM
+ ttrss_feeds WHERE hidden = false AND owner_uid = ".$_SESSION["uid"]);
+
+ while ($line = db_fetch_assoc($result)) {
+
+ $has_icon = (int) feed_has_icon($line["id"]);
+
+ print "<feed has_icon=\"$has_icon\"
+ cat_id=\"".(int)$line["cat_id"]."\" id=\"".$line["id"]."\"><![CDATA[";
+ print $line["title"];
+ print "]]></feed>";
+ }
+
+ print "</feeds>";
+
+ print "<feed-categories>";
+
+ $result = db_query($link, "SELECT id, title, collapsed FROM
+ ttrss_feed_categories WHERE owner_uid = ".$_SESSION["uid"]);
+
+ print "<category id=\"0\" collapsed=\"".
+ (int)$_COOKIE["ttrss_vf_uclps"]."\"><![CDATA[";
+ print __("Uncategorized");
+ print "]]></category>";
+
+ print "<category id=\"-1\" collapsed=\"".
+ (int)$_COOKIE["ttrss_vf_vclps"]."\"><![CDATA[";
+ print __("Special");
+ print "]]></category>";
+
+ print "<category id=\"-2\" collapsed=\"".
+ (int)$_COOKIE["ttrss_vf_lclps"]."\"><![CDATA[";
+ print __("Labels");
+ print "]]></category>";
+
+ while ($line = db_fetch_assoc($result)) {
+ print "<category
+ id=\"".$line["id"]."\"
+ collapsed=\"".(int)sql_bool_to_bool($line["collapsed"])."\"><![CDATA[";
+ print $line["title"];
+ print "]]></category>";
+ }
+
+ print "</feed-categories>";
+
+ print "<labels>";
+
+ $result = db_query($link, "SELECT * FROM
+ ttrss_labels2 WHERE owner_uid = ".$_SESSION["uid"]);
+
+ while ($line = db_fetch_assoc($result)) {
+ print "<label
+ id=\"".$line["id"]."\"
+ fg_color=\"".$line["fg_color"]."\"
+ bg_color=\"".$line["bg_color"]."\"
+ ><![CDATA[";
+ print $line["caption"];
+ print "]]></label>";
+ }
+
+
+ print "</labels>";
+
+ }
+
+ if ($stage > 0) {
+ print "<articles>";
+
+ $limit = 50;
+ $skip = $limit*($stage-1);
+
+ print "<limit value=\"$limit\"/>";
+
+ if ($amount > 0) $amount -= $skip;
+
+ if ($amount > 0) {
+
+ $limit = min($limit, $amount);
+
+ if ($unread_only) {
+ $unread_qpart = "(unread = true OR marked = true) AND ";
+ }
+
+ if ($cidt && $cidb) {
+ $cid_qpart = "(id > $cidt OR id < $cidb) AND ";
+ }
+
+ if (DB_TYPE == "pgsql") {
+ $date_qpart = "updated >= NOW() - INTERVAL '1 month' AND";
+ } else {
+ $date_qpart = "updated >= DATE_SUB(NOW(), INTERVAL 1 MONTH) AND";
+ }
+
+ $result = db_query($link,
+ "SELECT DISTINCT ttrss_entries.id,ttrss_entries.title,
+ guid,link,comments,
+ feed_id,content,updated,unread,marked FROM
+ ttrss_user_entries,ttrss_entries,ttrss_feeds
+ WHERE $unread_qpart $cid_qpart $date_qpart
+ hidden = false AND
+ ttrss_feeds.id = feed_id AND
+ ref_id = ttrss_entries.id AND
+ ttrss_user_entries.owner_uid = ".$_SESSION["uid"]."
+ ORDER BY updated DESC LIMIT $limit OFFSET $skip");
+
+ if (function_exists('json_encode')) {
+
+ while ($line = db_fetch_assoc($result)) {
+ print "<article><![CDATA[";
+
+ $line["marked"] = (int)sql_bool_to_bool($line["marked"]);
+ $line["unread"] = (int)sql_bool_to_bool($line["unread"]);
+
+ $line["labels"] = get_article_labels($link, $line["id"]);
+
+// too slow :(
+// $line["tags"] = format_tags_string(
+// get_article_tags($link, $line["id"]), $line["id"]);
+
+ print json_encode($line);
+ print "]]></article>";
+ }
+ }
+
+ }
+
+ print "</articles>";
+
+ }
+
+ print "</rpc-reply>";
+
+ return;
+ }
+
print "<rpc-reply><error>Unknown method: $subop</error></rpc-reply>";
}
?>
diff --git a/modules/popup-dialog.php b/modules/popup-dialog.php
index 465d3bcd9..2ba0740c4 100644
--- a/modules/popup-dialog.php
+++ b/modules/popup-dialog.php
@@ -461,6 +461,48 @@
return;
}
+/* if ($id == "offlineDownload") {
+ print "<div id=\"infoBoxTitle\">".__('Download articles')."</div>";
+ print "<div class=\"infoBoxContents\">";
+
+ print "<form name='download_ops_form' id='download_ops_form'>";
+
+ print "<div class=\"dlgSec\">".__("Download")."</div>";
+
+ print "<div class=\"dlgSecCont\">";
+
+ $amount = array(
+ 50 => 50,
+ 100 => 100,
+ 250 => 250,
+ 500 => 500);
+
+ print_select_hash("amount", 50, $amount);
+
+ print " " . __("latest articles for offline reading.");
+
+ print "<br/>";
+
+ print "<input checked='yes' type='checkbox' name='unread_only' id='unread_only'>";
+ print "<label for='unread_only'>".__('Only include unread articles')."</label>";
+
+ print "</div>";
+
+ print "</form>";
+
+ print "<div class=\"dlgButtons\">
+ <input class=\"button\"
+ type=\"submit\" onclick=\"return initiate_offline_download(0, this)\" value=\"".__('Download')."\">
+ <input class=\"button\"
+ type=\"submit\" onclick=\"return closeInfoBox()\"
+ value=\"".__('Cancel')."\"></div>";
+
+ print "</div>";
+
+ return;
+ } */
+
+
print "<div id='infoBoxTitle'>Internal Error</div>
<div id='infoBoxContents'>
<p>Unknown dialog <b>$id</b></p>
diff --git a/offline.js b/offline.js
new file mode 100644
index 000000000..c2171d981
--- /dev/null
+++ b/offline.js
@@ -0,0 +1,1421 @@
+var SCHEMA_VERSION = 10;
+
+var offline_mode = false;
+var store = false;
+var localServer = false;
+var db = false;
+
+function view_offline(id, feed_id) {
+ try {
+
+ enableHotkeys();
+ showArticleInHeadlines(id);
+
+ db.execute("UPDATE articles SET unread = 0 WHERE id = ?", [id]);
+
+ var rs = db.execute("SELECT * FROM articles WHERE id = ?", [id]);
+
+ if (rs.isValidRow()) {
+
+ var tmp = "<div class=\"postReply\">";
+
+ tmp += "<div class=\"postHeader\" onmouseover=\"enable_resize(true)\" "+
+ "onmouseout=\"enable_resize(false)\">";
+
+ tmp += "<div class=\"postDate\">"+rs.fieldByName("updated")+"</div>";
+
+ if (rs.fieldByName("link") != "") {
+ tmp += "<div clear='both'><a target=\"_blank\" "+
+ "href=\"" + rs.fieldByName("link") + "\">" +
+ rs.fieldByName("title") + "</a></div>";
+ } else {
+ tmp += "<div clear='both'>" + rs.fieldByName("title") + "</div>";
+ }
+
+/* tmp += "<div style='float : right'> "+
+ "<img src='images/tag.png' class='tagsPic' alt='Tags' title='Tags'>";
+ tmp += rs.fieldByName("tags");
+ tmp += "</div>"; */
+
+/* tmp += "<div clear='both'>"+
+ "<a target=\"_blank\" "+
+ "href=\"" + rs.fieldByName("comments") + "\">" +
+ __("comments") + "</a></div>"; */
+
+ tmp += "</div>";
+
+ tmp += "<div class=\"postContent\">"
+ tmp += rs.fieldByName("content");
+ tmp += "</div>";
+
+ tmp += "</div>";
+
+ render_article(tmp);
+ update_local_feedlist_counters();
+ }
+
+ rs.close();
+
+ return false;
+
+ } catch (e) {
+ exception_error("view_offline", e);
+ }
+}
+
+function viewfeed_offline(feed_id, subop, is_cat, subop_param, skip_history, offset) {
+ try {
+ notify('');
+
+ if (!offset) offset = 0;
+
+ if (offset > 0) {
+ _feed_cur_page = parseInt(offset);
+ if (_infscroll_request_sent) {
+ return;
+ }
+ } else {
+ _feed_cur_page = 0;
+ _infscroll_disable = 0;
+ }
+
+ if (getActiveFeedId() != feed_id) {
+ _feed_cur_page = 0;
+ active_post_id = 0;
+ _infscroll_disable = 0;
+ }
+
+ loading_set_progress(100);
+
+ clean_feed_selections();
+
+ setActiveFeedId(feed_id, is_cat);
+
+ if (!is_cat) {
+ var feedr = document.getElementById("FEEDR-" + feed_id);
+ if (feedr && !feedr.className.match("Selected")) {
+ feedr.className = feedr.className + "Selected";
+ }
+ } else {
+ var feedr = document.getElementById("FCAT-" + feed_id);
+ if (feedr && !feedr.className.match("Selected")) {
+ feedr.className = feedr.className + "Selected";
+ }
+ }
+
+ if (subop == "MarkAllRead") {
+ catchup_local_feed(feed_id, is_cat);
+ }
+
+ disableContainerChildren("headlinesToolbar", false);
+ Form.enable("main_toolbar_form");
+
+ var f = document.getElementById("headlines-frame");
+ try {
+ if (reply.offset == 0) {
+ debug("resetting headlines scrollTop");
+ f.scrollTop = 0;
+ }
+ } catch (e) { };
+
+
+ var tmp = "";
+ var feed_title = "";
+
+ if (is_cat) {
+ feed_title = get_local_category_title(feed_id);
+ } else {
+ feed_title = get_local_feed_title(feed_id);
+ }
+
+ if (feed_title) {
+
+ if (offset == 0) {
+ tmp += "<div id=\"headlinesContainer\">";
+
+ tmp += "<div class=\"headlinesSubToolbar\">";
+ tmp += "<div id=\"subtoolbar_ftitle\">";
+ tmp += feed_title;
+ tmp += "</div>";
+
+ var sel_all_link;
+ var sel_unread_link;
+ var sel_none_link;
+ var sel_inv_link;
+
+ if (document.getElementById("content-frame")) {
+ sel_all_link = "javascript:selectTableRowsByIdPrefix('headlinesList', 'RROW-', 'RCHK-', true, '', true)";
+ sel_unread_link = "javascript:selectTableRowsByIdPrefix('headlinesList', 'RROW-', 'RCHK-', true, 'Unread', true)";
+ sel_none_link = "javascript:selectTableRowsByIdPrefix('headlinesList', 'RROW-', 'RCHK-', false)";
+ sel_inv_link = "javascript:invertHeadlineSelection()";
+ } else {
+ sel_all_link = "javascript:cdmSelectArticles('all')";
+ sel_unread_link = "javascript:cdmSelectArticles('unread')";
+ sel_none_link = "javascript:cdmSelectArticles('none')";
+ sel_inv_link = "javascript:invertHeadlineSelection()";
+ }
+
+ tmp += __('Select:')+
+ " <a href=\""+sel_all_link+"\">"+__('All')+"</a>, "+
+ "<a href=\""+sel_unread_link+"\">"+__('Unread')+"</a>, "+
+ "<a href=\""+sel_inv_link+"\">"+__('Invert')+"</a>, "+
+ "<a href=\""+sel_none_link+"\">"+__('None')+"</a>";
+
+ tmp += "&nbsp;&nbsp;";
+
+ tmp += "</div>";
+
+ tmp += "<div id=\"headlinesInnerContainer\" onscroll=\"headlines_scroll_handler()\">";
+ if (document.getElementById("content-frame")) {
+ tmp += "<table class=\"headlinesList\" id=\"headlinesList\" cellspacing=\"0\">";
+ }
+
+ }
+
+ var limit = 30;
+
+ var toolbar_form = document.forms["main_toolbar_form"];
+
+ var limit = toolbar_form.limit[toolbar_form.limit.selectedIndex].value;
+ var view_mode = toolbar_form.view_mode[toolbar_form.view_mode.selectedIndex].value;
+
+ var limit_qpart = "";
+ var strategy_qpart = "";
+ var mode_qpart = "";
+ var offset_qpart = "";
+
+ if (limit != 0) {
+ limit_qpart = "LIMIT " + limit;
+ }
+
+ if (view_mode == "all_articles") {
+ mode_qpart = "1";
+ } else if (view_mode == "adaptive") {
+ if (is_cat && get_local_category_unread(feed_id) ||
+ get_local_feed_unread(feed_id) > 0) {
+ mode_qpart = "unread = 1";
+ } else {
+ mode_qpart = "1";
+ }
+
+ } else if (view_mode == "marked") {
+ mode_qpart = "marked = 1";
+ } else if (view_mode == "unread") {
+ mode_qpart = "unread = 1";
+ } else {
+ mode_qpart = "1";
+ }
+
+ var ext_tables_qpart = "";
+
+ if (is_cat) {
+ if (feed_id >= 0) {
+ strategy_qpart = "cat_id = " + feed_id;
+ } else if (feed_id == -2) {
+ strategy_qpart = "article_labels.id = articles.id";
+ ext_tables_qpart = ",article_labels";
+ }
+ } else if (feed_id > 0) {
+ strategy_qpart = "feed_id = " + feed_id;
+ } else if (feed_id == -1) {
+ strategy_qpart = "marked = 1";
+ } else if (feed_id == -4) {
+ strategy_qpart = "1";
+ } else if (feed_id < -10) {
+ var label_id = -11 - feed_id;
+ strategy_qpart = "article_labels.id = articles.id AND label_id = " + label_id;
+ ext_tables_qpart = ",article_labels";
+ }
+
+ if (offset > 0) {
+ offset_qpart = "OFFSET " + (offset*30);
+ } else {
+ offset_qpart = "";
+ }
+
+ var query = "SELECT *,feeds.title AS feed_title "+
+ "FROM articles,feeds,categories"+ext_tables_qpart+" "+
+ "WHERE " +
+ "cat_id = categories.id AND " +
+ "feed_id = feeds.id AND " +
+ strategy_qpart +
+ " AND " + mode_qpart +
+ " ORDER BY updated DESC "+
+ limit_qpart + " " +
+ offset_qpart;
+
+ var rs = db.execute(query);
+
+ var line_num = offset*30;
+
+ var real_feed_id = feed_id;
+
+ while (rs.isValidRow()) {
+
+ var id = rs.fieldByName("id");
+ var feed_id = rs.fieldByName("feed_id");
+
+ var entry_feed_title = false;
+
+ if (real_feed_id < 0 || is_cat) {
+ entry_feed_title = rs.fieldByName("feed_title");
+ }
+
+ var marked_pic;
+
+ var row_class = (line_num % 2) ? "even" : "odd";
+
+ if (rs.fieldByName("unread") == "1") {
+ row_class += "Unread";
+ }
+
+ var labels = get_local_article_labels(id);
+
+ var labels_str = "<span id=\"HLLCTR-"+id+"\">";
+ labels_str += format_article_labels(labels, id);
+ labels_str += "</span>";
+
+ if (rs.fieldByName("marked") == "1") {
+ marked_pic = "<img id=\"FMPIC-"+id+"\" "+
+ "src=\"images/mark_set.png\" class=\"markedPic\""+
+ "alt=\"Unstar article\" onclick='javascript:tMark("+id+")'>";
+ } else {
+ marked_pic = "<img id=\"FMPIC-"+id+"\" "+
+ "src=\"images/mark_unset.png\" class=\"markedPic\""+
+ "alt=\"Star article\" onclick='javascript:tMark("+id+")'>";
+ }
+
+ var mouseover_attrs = "onmouseover='postMouseIn($id)' "+
+ "onmouseout='postMouseOut($id)'";
+
+ var content_preview = truncate_string(strip_tags(rs.fieldByName("content")),
+ 100);
+
+ if (document.getElementById("content-frame")) {
+
+ tmp += "<tr class='"+row_class+"' id='RROW-"+id+"' "+mouseover_attrs+">";
+
+ tmp += "<td class='hlUpdPic'> </td>";
+
+ tmp += "<td class='hlSelectRow'>"+
+ "<input type=\"checkbox\" onclick=\"tSR(this)\" id=\"RCHK-"+id+"\"></td>";
+
+ tmp += "<td class='hlMarkedPic'>"+marked_pic+"</td>";
+
+ tmp += "<td onclick='view("+id+","+feed_id+")' "+
+ "class='hlContent' valign='middle'>";
+
+ tmp += "<a target=\"_blank\" id=\"RTITLE-"+id+"\" href=\"" +
+ rs.fieldByName("link") + "\"" +
+ "onclick=\"return view("+id+","+feed_id+");\">"+
+ rs.fieldByName("title");
+
+ tmp += "<span class=\"contentPreview\"> - "+content_preview+"</span>";
+
+ tmp += "</a>";
+
+ tmp += labels_str;
+
+ if (entry_feed_title) {
+ tmp += " <span class=\"hlFeed\">"+
+ "(<a href='javascript:viewfeed("+feed_id+
+ ")'>"+entry_feed_title+"</a>)</span>";
+ }
+
+ tmp += "</td>";
+
+ tmp += "<td class=\"hlUpdated\" onclick='view("+id+","+feed_id+")'>"+
+ "<nobr>"+rs.fieldByName("updated").substring(0,16)+
+ "</nobr></td>";
+
+ tmp += "</tr>";
+ } else {
+
+ var add_class = "";
+
+ if (rs.fieldByName("unread") == "1") {
+ add_class = "Unread";
+ }
+
+ tmp += "<div class=\"cdmArticle"+add_class+"\" id=\"RROW-"+id+"\" "+
+ mouseover_attrs+"'>";
+
+ feed_icon_img = "<img class=\"tinyFeedIcon\" src=\""+
+ getInitParam("icons_url")+"/"+feed_id+".ico\" alt=\"\">";
+ cdm_feed_icon = "<span style=\"cursor : pointer\" "+
+ "onclick=\"viewfeed("+feed_id+")\">"+feed_icon_img+"</span>";
+
+ tmp += "<div class=\"cdmHeader\">";
+ tmp += "<div class=\"articleUpdated\">"+
+ rs.fieldByName("updated").substring(0,16)+
+ " "+cdm_feed_icon+"</div>";
+
+ tmp += "<span id=\"RTITLE-"+id+"\" class=\"titleWrap\">"+
+ "<a class=\"title\" onclick=\"javascript:toggleUnread("+id+", 0)\""+
+ "target=\"_blank\" href=\""+rs.fieldByName("link")+
+ "\">"+rs.fieldByName("title")+"</a>";
+
+ tmp += labels_str;
+
+ if (entry_feed_title) {
+ tmp += "&nbsp;(<a href='javascript:viewfeed("+feed_id+
+ ")'>"+entry_feed_title+"</a>)";
+ }
+
+ tmp += "</span></div>";
+
+ tmp += "<div class=\"cdmContent\" onclick=\"cdmClicked("+id+")\""+
+ "id=\"CICD-"+id+"\">";
+ tmp += rs.fieldByName("content");
+ tmp += "<br clear='both'>"
+ tmp += "</div>";
+
+ tmp += "<div class=\"cdmFooter\"><span class='s0'>";
+ tmp += __("Select:")+
+ " <input type=\"checkbox\" "+
+ "onclick=\"toggleSelectRowById(this, 'RROW-"+id+"')\" "+
+ "class=\"feedCheckBox\" id=\"RCHK-"+id+"\">";
+
+ tmp += "</span><span class='s1'>"+marked_pic+"</span> ";
+
+/* tmp += "<span class='s1'>"+
+ "<img class='tagsPic' src='images/tag.png' alt='Tags' title='Tags'>"+
+ "<span id=\"ATSTR-"+id+"\">"+rs.fieldByName("tags")+"</span>"+
+ "</span>"; */
+
+ tmp += "<span class='s2'>Toggle: <a class=\"cdmToggleLink\""+
+ "href=\"javascript:toggleUnread("+id+")\">"+
+ "Unread</a></span>";
+ tmp += "</div>";
+
+ tmp += "</div>";
+ }
+
+ rs.next();
+ line_num++;
+ }
+
+ if (line_num - offset*30 < 30) {
+ _infscroll_disable = 1;
+ }
+
+ rs.close();
+
+ if (offset == 0) {
+ tmp += "</table>";
+
+ if (line_num - offset*30 == 0) {
+ tmp += "<div class='whiteBox'>" +
+ __("No articles found to display.") +
+ "</div>";
+ }
+ tmp += "</div></div>";
+ }
+
+ if (offset == 0) {
+ var container = document.getElementById("headlines-frame");
+ container.innerHTML = tmp;
+ } else {
+ var ids = getSelectedArticleIds2();
+
+ var container = document.getElementById("headlinesList");
+ container.innerHTML = container.innerHTML + tmp;
+
+ for (var i = 0; i < ids.length; i++) {
+ markHeadline(ids[i]);
+ }
+ }
+ }
+
+ remove_splash();
+
+ _infscroll_request_sent = 0;
+
+ } catch (e) {
+ exception_error("viewfeed_offline", e);
+ }
+}
+
+function render_offline_feedlist() {
+ try {
+ var cats_enabled = getInitParam("enable_feed_cats") == "1";
+
+ var tmp = "<ul class=\"feedList\" id=\"feedList\">";
+
+ var unread = get_local_feed_unread(-4);
+
+ global_unread = unread;
+ updateTitle();
+
+ if (cats_enabled) {
+ tmp += printCategoryHeader(-1, is_local_cat_collapsed(-1), false);
+ }
+
+ tmp += printFeedEntry(-4, __("All articles"), "feed", unread,
+ "images/tag.png");
+
+ var unread = get_local_feed_unread(-1);
+
+ tmp += printFeedEntry(-1, __("Starred articles"), "feed", unread,
+ "images/mark_set.png");
+
+ if (cats_enabled) {
+ tmp += "</ul></li>";
+ } else {
+ tmp += "<li><hr/></li>";
+ }
+
+ if (cats_enabled) {
+ tmp += printCategoryHeader(-2, is_local_cat_collapsed(-2), false);
+ }
+
+ var rs = db.execute("SELECT id,caption "+
+ "FROM labels "+
+ "ORDER BY caption");
+
+ while (rs.isValidRow()) {
+ var id = -11 - parseInt(rs.field(0));
+ var caption = rs.field(1);
+ var unread = get_local_feed_unread(id);
+
+ tmp += printFeedEntry(id, caption, "feed", unread,
+ "images/label.png");
+
+ rs.next();
+ }
+
+ rs.close();
+
+ if (cats_enabled) {
+ tmp += "</ul></li>";
+ } else {
+ tmp += "<li><hr/></li>";
+ }
+
+/* var rs = db.execute("SELECT feeds.id,feeds.title,has_icon,COUNT(articles.id) "+
+ "FROM feeds LEFT JOIN articles ON (feed_id = feeds.id) "+
+ "WHERE unread = 1 OR unread IS NULL GROUP BY feeds.id "+
+ "ORDER BY feeds.title"); */
+
+ var order_by = "feeds.title";
+
+ if (cats_enabled) order_by = "categories.title," + order_by;
+
+ var rs = db.execute("SELECT "+
+ "feeds.id,feeds.title,has_icon,cat_id,collapsed "+
+ "FROM feeds,categories WHERE cat_id = categories.id "+
+ "ORDER BY "+order_by);
+
+ var tmp_cat_id = -1;
+
+ while (rs.isValidRow()) {
+
+ var id = rs.field(0);
+ var title = rs.field(1);
+ var has_icon = rs.field(2);
+ var unread = get_local_feed_unread(id);
+ var cat_id = rs.field(3);
+ var cat_hidden = rs.field(4);
+
+ if (cat_id != tmp_cat_id && cats_enabled) {
+ if (tmp_cat_id != -1) {
+ tmp += "</ul></li>";
+ }
+ tmp += printCategoryHeader(cat_id, cat_hidden, true);
+ tmp_cat_id = cat_id;
+ }
+
+ var icon = "";
+
+ if (has_icon) {
+ icon = getInitParam("icons_url") + "/" + id + ".ico";
+ }
+
+ var feed_icon = "";
+
+ var row_class = "feed";
+
+ if (unread > 0) {
+ row_class += "Unread";
+ fctr_class = "feedCtrHasUnread";
+ } else {
+ fctr_class = "feedCtrNoUnread";
+ }
+
+ tmp += printFeedEntry(id, title, "feed", unread, icon);
+
+ rs.next();
+ }
+
+ rs.close();
+
+ if (cats_enabled) {
+ tmp += "</ul>";
+ }
+
+ tmp += "</ul>";
+
+ render_feedlist(tmp);
+ } catch (e) {
+ exception_error("render_offline_feedlist", e);
+ }
+}
+
+function init_offline() {
+ try {
+ offline_mode = true;
+
+ Element.hide("dispSwitchPrompt");
+ Element.hide("feedBrowserPrompt");
+
+ Element.hide("topLinksOnline");
+ Element.show("topLinksOffline");
+
+ var tb_form = document.getElementById("main_toolbar_form");
+ Element.hide(tb_form.update);
+
+ var chooser = document.getElementById("quickMenuChooser");
+ chooser.disabled = true;
+
+ var rs = db.execute("SELECT key, value FROM init_params");
+
+ while (rs.isValidRow()) {
+ init_params[rs.field(0)] = rs.field(1);
+ rs.next();
+ }
+
+ rs.close();
+
+ var rs = db.execute("SELECT COUNT(*) FROM feeds");
+
+ var num_feeds = 0;
+
+ if (rs.isValidRow()) {
+ num_feeds = rs.field(0);
+ }
+
+ rs.close();
+
+ if (num_feeds == 0) {
+ remove_splash();
+ return fatalError(0,
+ __("Data for offline browsing has not been downloaded yet."));
+ }
+
+ render_offline_feedlist();
+ init_second_stage();
+ window.setTimeout("viewfeed(-4)", 50);
+
+ } catch (e) {
+ exception_error("init_offline", e);
+ }
+}
+
+function offline_download_parse(stage, transport) {
+ try {
+ if (transport.responseXML) {
+
+ var sync_ok = transport.responseXML.getElementsByTagName("sync-ok");
+
+ if (sync_ok.length > 0) {
+ for (var i = 0; i < sync_ok.length; i++) {
+ var id = sync_ok[i].getAttribute("id");
+ if (id) {
+ debug("synced offline info for id " + id);
+ db.execute("UPDATE articles SET modified = '' WHERE id = ?", [id]);
+ }
+ }
+ }
+
+ if (stage == 0) {
+
+ var feeds = transport.responseXML.getElementsByTagName("feed");
+
+ if (feeds.length > 0) {
+ db.execute("DELETE FROM feeds");
+ }
+
+ for (var i = 0; i < feeds.length; i++) {
+ var id = feeds[i].getAttribute("id");
+ var has_icon = feeds[i].getAttribute("has_icon");
+ var title = feeds[i].firstChild.nodeValue;
+ var cat_id = feeds[i].getAttribute("cat_id");
+
+ db.execute("INSERT INTO feeds (id,title,has_icon,cat_id)"+
+ "VALUES (?,?,?,?)",
+ [id, title, has_icon, cat_id]);
+ }
+
+ var cats = transport.responseXML.getElementsByTagName("category");
+
+ if (feeds.length > 0) {
+ db.execute("DELETE FROM categories");
+ }
+
+ for (var i = 0; i < cats.length; i++) {
+ var id = cats[i].getAttribute("id");
+ var collapsed = cats[i].getAttribute("collapsed");
+ var title = cats[i].firstChild.nodeValue;
+
+ db.execute("INSERT INTO categories (id,title,collapsed)"+
+ "VALUES (?,?,?)",
+ [id, title, collapsed]);
+ }
+
+ var labels = transport.responseXML.getElementsByTagName("label");
+
+ if (labels.length > 0) {
+ db.execute("DELETE FROM labels");
+ }
+
+ for (var i = 0; i < labels.length; i++) {
+ var id = labels[i].getAttribute("id");
+ var fg_color = labels[i].getAttribute("fg_color");
+ var bg_color = labels[i].getAttribute("bg_color");
+ var caption = labels[i].firstChild.nodeValue;
+
+ db.execute("INSERT INTO labels (id,caption,fg_color,bg_color)"+
+ "VALUES (?,?,?,?)",
+ [id, caption, fg_color, bg_color]);
+ }
+
+ window.setTimeout("update_offline_data("+(stage+1)+")", 10*1000);
+ } else {
+
+ var articles = transport.responseXML.getElementsByTagName("article");
+
+ var limit = transport.responseXML.getElementsByTagName("limit")[0];
+
+ if (limit) {
+ limit = limit.getAttribute("value");
+ } else {
+ limit = 0;
+ }
+
+ var articles_found = 0;
+
+ for (var i = 0; i < articles.length; i++) {
+ var a = eval("("+articles[i].firstChild.nodeValue+")");
+ articles_found++;
+ if (a) {
+
+ db.execute("DELETE FROM articles WHERE id = ?", [a.id]);
+
+ db.execute("INSERT INTO articles "+
+ "(id, feed_id, title, link, guid, updated, content, "+
+ "unread, marked, tags, comments) "+
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
+ [a.id, a.feed_id, a.title, a.link, a.guid, a.updated,
+ a.content, a.unread, a.marked, a.tags,
+ a.comments]);
+
+ if (a.labels.length > 0) {
+ for (var j = 0; j < a.labels.length; j++) {
+ label_local_add_article(a.id, a.labels[j][0]);
+ }
+ }
+
+ }
+ }
+
+ debug("downloaded articles: " + articles_found + " limit: " + limit);
+
+ var has_sync_data = has_local_sync_data();
+
+ if (articles_found >= limit || has_sync_data) {
+ window.setTimeout("update_offline_data("+(stage+1)+")", 10*1000);
+ debug("<b>update_offline_data: done " + stage + " HSD: " +
+ has_sync_data + "</b>");
+ } else {
+ window.setTimeout("update_offline_data(0)", 180*1000);
+ debug("update_offline_data: finished");
+
+ db.execute("DELETE FROM articles WHERE "+
+ "updated < DATETIME('NOW', 'localtime', '-31 days')");
+
+ }
+ }
+
+ update_local_sync_data();
+
+// notify('');
+
+ }
+ } catch (e) {
+ exception_error("offline_download_parse", e);
+ }
+}
+
+function update_offline_data(stage) {
+ try {
+
+ if (!stage) stage = 0;
+ if (offline_mode) return;
+
+ debug("update_offline_data: stage " + stage);
+
+// notify_progress("Updating offline data... (" + stage +")", true);
+
+ var query = "backend.php?op=rpc&subop=download&stage=" + stage;
+
+ var rs = db.execute("SELECT MAX(id), MIN(id) FROM articles");
+
+ if (rs.isValidRow() && rs.field(0)) {
+ var offline_dl_max_id = rs.field(0);
+ var offline_dl_min_id = rs.field(1);
+
+ query = query + "&cidt=" + offline_dl_max_id;
+ query = query + "&cidb=" + offline_dl_min_id;
+ }
+
+ rs.close();
+
+ var to_sync = prepare_local_sync_data();
+
+ if (to_sync != "") {
+ to_sync = "?sync=" + param_escape(to_sync);
+ }
+
+ debug(query + "/" + to_sync);
+
+ new Ajax.Request(query, {
+ parameters: to_sync,
+ onComplete: function(transport) {
+ offline_download_parse(stage, transport);
+ } });
+
+ } catch (e) {
+ exception_error("initiate_offline_download", e);
+ }
+}
+
+function set_feedlist_counter(id, ctr, is_cat) {
+ try {
+
+ var feedctr = document.getElementById("FEEDCTR-" + id);
+ var feedu = document.getElementById("FEEDU-" + id);
+ var feedr = document.getElementById("FEEDR-" + id);
+
+ if (is_cat) {
+ var catctr = document.getElementById("FCATCTR-" + id);
+ if (catctr) {
+ catctr.innerHTML = "(" + ctr + ")";
+ if (ctr > 0) {
+ catctr.className = "catCtrHasUnread";
+ } else {
+ catctr.className = "catCtrNoUnread";
+ }
+ }
+ } else if (feedctr && feedu && feedr) {
+
+ var row_needs_hl = (ctr > 0 && ctr > parseInt(feedu.innerHTML));
+
+ feedu.innerHTML = ctr;
+
+ if (ctr > 0) {
+ feedctr.className = "feedCtrHasUnread";
+ if (!feedr.className.match("Unread")) {
+ var is_selected = feedr.className.match("Selected");
+
+ feedr.className = feedr.className.replace("Selected", "");
+ feedr.className = feedr.className.replace("Unread", "");
+
+ feedr.className = feedr.className + "Unread";
+
+ if (is_selected) {
+ feedr.className = feedr.className + "Selected";
+ }
+
+ }
+
+ if (row_needs_hl) {
+ new Effect.Highlight(feedr, {duration: 1, startcolor: "#fff7d5",
+ queue: { position:'end', scope: 'EFQ-' + id, limit: 1 } } );
+ }
+ } else {
+ feedctr.className = "feedCtrNoUnread";
+ feedr.className = feedr.className.replace("Unread", "");
+ }
+ }
+
+ } catch (e) {
+ exception_error("set_feedlist_counter", e);
+ }
+}
+
+function update_local_feedlist_counters() {
+ try {
+ if (!offline_mode || !db) return;
+
+/* var rs = db.execute("SELECT feeds.id,COUNT(articles.id) "+
+ "FROM feeds LEFT JOIN articles ON (feed_id = feeds.id) "+
+ "WHERE unread = 1 OR unread IS NULL GROUP BY feeds.id "+
+ "ORDER BY feeds.title"); */
+
+ var rs = db.execute("SELECT id FROM feeds "+
+ "ORDER BY title");
+
+ while (rs.isValidRow()) {
+ var id = rs.field(0);
+ var ctr = get_local_feed_unread(id);
+ set_feedlist_counter(id, ctr, false);
+ rs.next();
+ }
+
+ rs.close();
+
+ var rs = db.execute("SELECT cat_id,SUM(unread) "+
+ "FROM articles, feeds WHERE feeds.id = feed_id GROUP BY cat_id");
+
+ while (rs.isValidRow()) {
+ var id = rs.field(0);
+ var ctr = rs.field(1);
+ set_feedlist_counter(id, ctr, true);
+ rs.next();
+ }
+
+ rs.close();
+
+ set_feedlist_counter(-2, get_local_category_unread(-2), true);
+
+ set_feedlist_counter(-4, get_local_feed_unread(-4));
+ set_feedlist_counter(-1, get_local_feed_unread(-1));
+
+ var rs = db.execute("SELECT id FROM labels");
+
+ while (rs.isValidRow()) {
+ var id = -11 - rs.field(0);
+ var ctr = get_local_feed_unread(id);
+ set_feedlist_counter(id, ctr, false);
+ rs.next();
+ }
+
+ rs.close();
+
+ hideOrShowFeeds(getInitParam("hide_read_feeds") == 1);
+
+ global_unread = get_local_feed_unread(-4);
+ updateTitle();
+
+ } catch (e) {
+ exception_error("update_local_feedlist_counters", e);
+ }
+}
+
+function get_local_feed_unread(id) {
+ try {
+ var rs;
+
+ if (id == -4) {
+ rs = db.execute("SELECT SUM(unread) FROM articles");
+ } else if (id == -1) {
+ rs = db.execute("SELECT SUM(unread) FROM articles WHERE marked = 1");
+ } else if (id > 0) {
+ rs = db.execute("SELECT SUM(unread) FROM articles WHERE feed_id = ?", [id]);
+ } else if (id < -10) {
+ var label_id = -11 - id;
+ rs = db.execute("SELECT SUM(unread) FROM articles,article_labels "+
+ "WHERE article_labels.id = articles.id AND label_id = ?", [label_id]);
+ }
+
+ var a = false;
+
+ if (rs.isValidRow()) {
+ a = rs.field(0);
+ } else {
+ a = 0;
+ }
+
+ rs.close();
+
+ return a;
+
+ } catch (e) {
+ exception_error("get_local_feed_unread", e);
+ }
+}
+
+function init_gears() {
+ try {
+
+ if (window.google && google.gears) {
+ localServer = google.gears.factory.create("beta.localserver");
+ store = localServer.createManagedStore("tt-rss");
+ db = google.gears.factory.create('beta.database');
+ db.open('tt-rss');
+
+ db.execute("CREATE TABLE IF NOT EXISTS version (schema_version text)");
+
+ var rs = db.execute("SELECT schema_version FROM version");
+
+ var version = "";
+
+ if (rs.isValidRow()) {
+ version = rs.field(0);
+ }
+
+ rs.close();
+
+ if (version != SCHEMA_VERSION) {
+ db.execute("DROP TABLE IF EXISTS init_params");
+ db.execute("DROP TABLE IF EXISTS cache");
+ db.execute("DROP TABLE IF EXISTS feeds");
+ db.execute("DROP TABLE IF EXISTS categories");
+ db.execute("DROP TABLE IF EXISTS labels");
+ db.execute("DROP TABLE IF EXISTS article_labels");
+ db.execute("DROP TABLE IF EXISTS articles");
+ db.execute("DROP INDEX IF EXISTS article_labels_label_id_idx");
+ db.execute("DROP INDEX IF EXISTS articles_unread_idx");
+ db.execute("DROP INDEX IF EXISTS articles_feed_id_idx");
+ db.execute("DROP INDEX IF EXISTS articles_id_idx");
+ db.execute("DROP INDEX IF EXISTS article_labels_id_idx");
+ db.execute("DROP TABLE IF EXISTS version");
+ db.execute("DROP TRIGGER IF EXISTS articles_update_unread");
+ db.execute("DROP TRIGGER IF EXISTS articles_update_marked");
+ db.execute("DROP TRIGGER IF EXISTS articles_remove_labelrefs");
+ db.execute("CREATE TABLE IF NOT EXISTS version (schema_version text)");
+ db.execute("DROP TABLE IF EXISTS syncdata");
+ db.execute("INSERT INTO version (schema_version) VALUES (?)",
+ [SCHEMA_VERSION]);
+ }
+
+ db.execute("CREATE TABLE IF NOT EXISTS init_params (key text, value text)");
+
+ db.execute("CREATE TABLE IF NOT EXISTS cache (id integer, article text, param text, added text)");
+ db.execute("CREATE TABLE IF NOT EXISTS feeds (id integer, title text, has_icon integer, cat_id integer)");
+ db.execute("CREATE TABLE IF NOT EXISTS categories (id integer, title text, collapsed integer)");
+ db.execute("CREATE TABLE IF NOT EXISTS labels (id integer, caption text, fg_color text, bg_color text)");
+ db.execute("CREATE TABLE IF NOT EXISTS article_labels (id integer, label_id integer)");
+ db.execute("CREATE TABLE IF NOT EXISTS articles (id integer, feed_id integer, title text, link text, guid text, updated timestamp, content text, tags text, unread integer, marked integer, added text, modified timestamp, comments text)");
+
+ db.execute("CREATE INDEX IF NOT EXISTS articles_unread_idx ON articles(unread)");
+ db.execute("CREATE INDEX IF NOT EXISTS article_labels_label_id_idx ON article_labels(label_id)");
+ db.execute("CREATE INDEX IF NOT EXISTS articles_feed_id_idx ON articles(feed_id)");
+ db.execute("CREATE INDEX IF NOT EXISTS articles_id_idx ON articles(id)");
+ db.execute("CREATE INDEX IF NOT EXISTS article_labels_id_idx ON article_labels(id)");
+
+ db.execute("CREATE TABLE IF NOT EXISTS syncdata (key integer, value text)");
+
+ db.execute("DELETE FROM cache WHERE id LIKE 'F:%' OR id LIKE 'C:%'");
+
+ db.execute("CREATE TRIGGER IF NOT EXISTS articles_update_unread "+
+ "UPDATE OF unread ON articles "+
+ "BEGIN "+
+ "UPDATE articles SET modified = DATETIME('NOW', 'localtime') "+
+ "WHERE id = OLD.id AND "+
+ "OLD.unread != NEW.unread;"+
+ "END;");
+
+ db.execute("CREATE TRIGGER IF NOT EXISTS articles_update_marked "+
+ "UPDATE OF marked ON articles "+
+ "BEGIN "+
+ "UPDATE articles SET modified = DATETIME('NOW', 'localtime') "+
+ "WHERE id = OLD.id;"+
+ "END;");
+
+ db.execute("CREATE TRIGGER IF NOT EXISTS articles_remove_labelrefs "+
+ "DELETE ON articles "+
+ "BEGIN "+
+ "DELETE FROM article_labels WHERE id = OLD.id; "+
+ "END; ");
+
+ init_local_sync_data();
+
+ Element.show("restartOfflinePic");
+
+ }
+
+ cache_expire();
+
+ } catch (e) {
+ exception_error("init_gears", e);
+ }
+}
+
+function gotoOffline() {
+ window.location.href = "tt-rss.php?offline=1";
+}
+
+function gotoOnline() {
+ window.location.href = "tt-rss.php";
+}
+
+function local_collapse_cat(id) {
+ try {
+ if (db) {
+ db.execute("UPDATE categories SET collapsed = NOT collapsed WHERE id = ?",
+ [id]);
+ }
+ } catch (e) {
+ exception_error("local_collapse_cat", e);
+ }
+}
+
+function get_local_category_title(id) {
+ try {
+
+ var rs = db.execute("SELECT title FROM categories WHERE id = ?", [id]);
+ var tmp = "";
+
+ if (rs.isValidRow()) {
+ tmp = rs.field(0);
+ }
+
+ rs.close();
+
+ return tmp;
+
+ } catch (e) {
+ exception_error("get_local_category_title", e);
+ }
+}
+
+function get_local_category_unread(id) {
+ try {
+ var rs = false;
+
+ if (id >= 0) {
+ rs = db.execute("SELECT SUM(unread) FROM articles, feeds "+
+ "WHERE feeds.id = feed_id AND cat_id = ?", [id]);
+ } else if (id == -2) {
+ rs = db.execute("SELECT SUM(unread) FROM article_labels, articles "+
+ "where article_labels.id = articles.id");
+ } else {
+ return 0;
+ }
+
+ var tmp = 0;
+
+ if (rs.isValidRow()) {
+ tmp = rs.field(0);
+ }
+
+ rs.close();
+
+ return tmp;
+
+ } catch (e) {
+ exception_error("get_local_category_unread", e);
+ }
+}
+
+function printCategoryHeader(cat_id, hidden, can_browse) {
+ try {
+ if (hidden == undefined) hidden = false;
+ if (can_browse == undefined) can_browse = false;
+
+ var tmp_category = get_local_category_title(cat_id);
+ var tmp = "";
+
+ var cat_unread = get_local_category_unread(cat_id);
+
+ var holder_style = "";
+ var ellipsis = "";
+
+ if (hidden) {
+ holder_style = "display:none;";
+ ellipsis = "…";
+ }
+
+ var catctr_class = (cat_unread > 0) ? "catCtrHasUnread" : "catCtrNoUnread";
+
+ var browse_cat_link = "";
+ var inner_title_class = "catTitleNL";
+
+ if (can_browse) {
+ browse_cat_link = "onclick=\"javascript:viewCategory("+cat_id+")\"";
+ inner_title_class = "catTitle";
+ }
+
+ var cat_class = "feedCat";
+
+ tmp += "<li class=\""+cat_class+"\" id=\"FCAT-"+cat_id+"\">"+
+ "<img onclick=\"toggleCollapseCat("+cat_id+")\" class=\"catCollapse\""+
+ " title=\""+__('Click to collapse category')+"\""+
+ " src=\"images/cat-collapse.png\"><span class=\""+inner_title_class+"\" "+
+ " id=\"FCATN-"+cat_id+"\" "+browse_cat_link+
+ "\">"+tmp_category+"</span>";
+
+ tmp += "<span id=\"FCAP-"+cat_id+"\">";
+
+ tmp += " <span id=\"FCATCTR-"+cat_id+"\" "+
+ "class=\""+catctr_class+"\">("+cat_unread+")</span> "+ellipsis;
+
+ tmp += "</span>";
+
+ tmp += "<ul class=\"feedCatList\" id=\"FCATLIST-"+cat_id+"\" "+
+ "style='"+holder_style+"'>";
+
+ return tmp;
+ } catch (e) {
+ exception_error("printCategoryHeader", e);
+ }
+}
+
+function is_local_cat_collapsed(id) {
+ try {
+
+ var rs = db.execute("SELECT collapsed FROM categories WHERE id = ?", [id]);
+ var cat_hidden = 0;
+
+ if (rs.isValidRow()) {
+ cat_hidden = rs.field(0);
+ }
+
+ rs.close();
+
+ return cat_hidden == "1";
+
+ } catch (e) {
+ exception_error("is_local_cat_collapsed", e);
+ }
+}
+
+function get_local_article_labels(id) {
+ try {
+ var rs = db.execute("SELECT DISTINCT label_id,caption,fg_color,bg_color "+
+ "FROM labels, article_labels "+
+ "WHERE labels.id = label_id AND article_labels.id = ?", [id]);
+
+ var tmp = new Array();
+
+ while (rs.isValidRow()) {
+ var e = new Array();
+
+ e[0] = rs.field(0);
+ e[1] = rs.field(1);
+ e[2] = rs.field(2);
+ e[3] = rs.field(3);
+
+ tmp.push(e);
+
+ rs.next();
+ }
+
+ return tmp;
+
+ } catch (e) {
+ exception_error("get_local_article_labels", e);
+ }
+}
+
+function label_local_add_article(id, label_id) {
+ try {
+ //debug("label_local_add_article " + id + " => " + label_id);
+
+ var rs = db.execute("SELECT COUNT(id) FROM article_labels WHERE "+
+ "id = ? AND label_id = ?", [id, label_id]);
+ var check = rs.field(0);
+
+ if (rs.isValidRow()) {
+ var check = rs.field(0);
+ }
+ rs.close();
+
+ if (check == 0) {
+ db.execute("INSERT INTO article_labels (id, label_id) VALUES "+
+ "(?,?)", [id, label_id]);
+ }
+
+ } catch (e) {
+ exception_error("label_local_add_article", e);
+ }
+}
+
+function get_local_feed_title(id) {
+ try {
+
+ var feed_title = "Unknown feed: " + id;
+
+ if (id > 0) {
+ var rs = db.execute("SELECT title FROM feeds WHERE id = ?", [id]);
+
+ if (rs.isValidRow()) {
+ feed_title = rs.field(0);
+ }
+
+ rs.close();
+ } else if (id == -1) {
+ feed_title = __("Starred articles");
+ } else if (id == -4) {
+ feed_title = __("All articles");
+ } else if (id < -10) {
+
+ var label_id = -11 - id;
+
+ var rs = db.execute("SELECT caption FROM labels WHERE id = ?", [label_id]);
+
+ if (rs.isValidRow()) {
+ feed_title = rs.field(0);
+ }
+
+ rs.close();
+ }
+
+ return feed_title;
+
+ } catch (e) {
+ exception_error("get_local_feed_title", e);
+ }
+}
+
+function format_article_labels(labels, id) {
+ try {
+
+ var labels_str = "";
+
+ if (!labels) return "";
+
+ for (var i = 0; i < labels.length; i++) {
+ var l = labels[i];
+
+ labels_str += "<span class='hlLabelRef' "+
+ "style='color : "+l[2]+"; background-color : "+l[3]+"'>"+l[1]+"</span>";
+ }
+
+ return labels_str;
+
+ } catch (e) {
+ exception_error("format_article_labels", e);
+ }
+}
+
+function init_local_sync_data() {
+ try {
+ var rs = db.execute("SELECT COUNT(*) FROM syncdata WHERE key = 'last_online'");
+ var has_last_online = 0;
+
+ if (rs.isValidRow()) {
+ has_last_online = rs.field(0);
+ }
+
+ rs.close();
+
+ if (!has_last_online) {
+ db.execute("INSERT INTO syncdata (key, value) VALUES ('last_online', '')");
+ }
+
+ } catch (e) {
+ exception_error("init_local_sync_data", e);
+
+ }
+}
+
+function has_local_sync_data() {
+ try {
+
+ var rs = db.execute("SELECT id FROM articles "+
+ "WHERE modified > (SELECT value FROM syncdata WHERE key = 'last_online') "+
+ "LIMIT 1");
+
+ var tmp = 0;
+
+ if (rs.isValidRow()) {
+ tmp = rs.field(0);
+ }
+
+ rs.close();
+
+ return tmp != 0;
+
+ } catch (e) {
+ exception_error("has_local_sync_data", e);
+ }
+}
+
+function prepare_local_sync_data() {
+ try {
+ var rs = db.execute("SELECT value FROM syncdata WHERE key = 'last_online'");
+
+ var last_online = "";
+
+ if (rs.isValidRow()) {
+ last_online = rs.field(0);
+ }
+
+ rs.close();
+
+ var rs = db.execute("SELECT id,unread,marked FROM articles "+
+ "WHERE modified > ? LIMIT 200", [last_online]);
+
+ var tmp = last_online + ";";
+
+ var entries = 0;
+
+ while (rs.isValidRow()) {
+ var e = new Array();
+
+ tmp = tmp + rs.field(0) + "," + rs.field(1) + "," + rs.field(2) + ";";
+ entries++;
+
+ rs.next();
+ }
+
+ rs.close();
+
+ if (entries > 0) {
+ return tmp;
+ } else {
+ return '';
+ }
+
+ } catch (e) {
+ exception_error("prepare_local_sync_data", e);
+ }
+}
+
+function update_local_sync_data() {
+ try {
+ if (db && !offline_mode) {
+
+ var rs = db.execute("SELECT id FROM articles "+
+ "WHERE modified > (SELECT value FROM syncdata WHERE "+
+ "key = 'last_online') LIMIT 1")
+
+ var f_id = 0;
+
+ if (rs.isValidRow()) {
+ f_id = rs.field(0);
+ }
+
+ rs.close();
+
+ /* no pending articles to sync */
+
+ if (f_id == 0) {
+ db.execute("UPDATE syncdata SET value = DATETIME('NOW', 'localtime') "+
+ "WHERE key = 'last_online'");
+ }
+
+ }
+ } catch (e) {
+ exception_error("update_local_sync_data", e);
+ }
+}
+
+function catchup_local_feed(id, is_cat) {
+ try {
+ if (!db) return;
+
+ if (!is_cat) {
+ if (id >= 0) {
+ db.execute("UPDATE articles SET unread = 0 WHERE feed_id = ?", [id]);
+ } else if (id == -1) {
+ db.execute("UPDATE articles SET unread = 0 WHERE marked = 1");
+ } else if (id == -4) {
+ db.execute("UPDATE articles SET unread = 0");
+ } else if (id < -10) {
+ var label_id = -11-id;
+
+ db.execute("UPDATE articles SET unread = 0 WHERE "+
+ "(SELECT COUNT(*) FROM article_labels WHERE "+
+ "article_labels.id = articles.id AND label_id = ?) > 0", [label_id]);
+ }
+ }
+
+ update_local_feedlist_counters();
+
+ } catch (e) {
+ exception_error("catchup_local_feed", e);
+ }
+}
diff --git a/tt-rss.css b/tt-rss.css
index 95938bc91..3fbf51fc0 100644
--- a/tt-rss.css
+++ b/tt-rss.css
@@ -1698,10 +1698,13 @@ a.feedUpdErrLink {
display : none;
}
-#newVersionIcon {
+div.topLinks img {
vertical-align : middle;
cursor : pointer;
- margin-left : 10px;
+}
+
+#restartOfflinePic {
+ margin-left : 5px;
}
a.helpLinkPic {
@@ -1763,7 +1766,6 @@ div.autocomplete ul li {
div.topLinks {
float : right;
- margin-right : 5px;
color : gray;
}
diff --git a/tt-rss.js b/tt-rss.js
index 17e33bf85..5d104a069 100644
--- a/tt-rss.js
+++ b/tt-rss.js
@@ -1,3 +1,4 @@
+
var total_unread = 0;
var first_run = true;
var display_tags = false;
@@ -131,10 +132,18 @@ function backend_sanity_check_callback(transport) {
}
if (!transport.responseXML) {
- fatalError(3, "Sanity check: Received reply is not XML", transport.responseText);
+ if (!window.google && !google.gears) {
+ fatalError(3, "Sanity check: Received reply is not XML", transport.responseText);
+ } else {
+ init_offline();
+ }
return;
}
+ if (getURLParam("offline")) {
+ return init_offline();
+ }
+
var reply = transport.responseXML.firstChild.firstChild;
if (!reply) {
@@ -161,6 +170,13 @@ function backend_sanity_check_callback(transport) {
var v = param.getAttribute("value");
debug(k + " => " + v);
init_params[k] = v;
+
+ if (db) {
+ db.execute("DELETE FROM init_params WHERE key = ?", [k]);
+ db.execute("INSERT INTO init_params (key,value) VALUES (?, ?)",
+ [k, v]);
+ }
+
param = param.nextSibling;
}
}
@@ -226,6 +242,8 @@ function updateFeedList(silent, fetch) {
debug("<b>updateFeedList</b>");
+ if (offline_mode) return render_offline_feedlist();
+
var query_str = "backend.php?op=feeds";
if (display_tags) {
@@ -369,6 +387,8 @@ function init() {
if (arguments.callee.done) return;
arguments.callee.done = true;
+ init_gears();
+
disableContainerChildren("headlinesToolbar", true);
Form.disable("main_toolbar_form");
@@ -500,6 +520,19 @@ function init_second_stage() {
daemon_refresh_only = getInitParam("daemon_refresh_only") == 1;
feeds_sort_by_unread = getInitParam("feeds_sort_by_unread") == 1;
+/* var fl = cache_find_param("FEEDLIST", getInitParam("num_feeds"));
+
+ if (fl) {
+ render_feedlist(fl);
+ if (document.getElementById("feedList")) {
+ request_counters();
+ } else {
+ setTimeout('updateFeedList(false, false)', 50);
+ }
+ } else {
+ setTimeout('updateFeedList(false, false)', 50);
+ } */
+
setTimeout('updateFeedList(false, false)', 50);
debug("second stage ok");
@@ -520,6 +553,8 @@ function init_second_stage() {
resize_headlines();
+ window.setTimeout("update_offline_data(0)", 100);
+
} catch (e) {
exception_error("init_second_stage", e);
}
@@ -641,6 +676,11 @@ function quickMenuGo(opid) {
resize_headlines();
}
+/* if (opid == "qmcDownload") {
+ displayDlg("offlineDownload");
+ return;
+ } */
+
if (opid == "qmcResetCats") {
if (confirm(__("Reset category order?"))) {
@@ -720,6 +760,10 @@ function parse_runtime_info(elem) {
debug("RI: " + k + " => " + v);
+ if (k == "num_feeds") {
+ init_params[k] = v;
+ }
+
if (k == "new_version_available") {
var icon = document.getElementById("newVersionIcon");
if (icon) {
@@ -1270,6 +1314,16 @@ function hotkey_handler(e) {
}
}
+/* if (keycode == 68 && shift_key) { // D
+ initiate_offline_download();
+ return false;
+ }
+
+ if (keycode == 68) { // d
+ displayDlg("offlineDownload");
+ return false;
+ } */
+
if (keycode == 87) { // w
feeds_sort_by_unread = !feeds_sort_by_unread;
return resort_feedlist();
@@ -1451,3 +1505,4 @@ function feedBrowserSubscribe() {
}
}
+
diff --git a/tt-rss.php b/tt-rss.php
index 4ca516615..f4cb83549 100644
--- a/tt-rss.php
+++ b/tt-rss.php
@@ -48,6 +48,9 @@
<script type="text/javascript" charset="utf-8" src="functions.js?<?php echo $dt_add ?>"></script>
<script type="text/javascript" charset="utf-8" src="feedlist.js?<?php echo $dt_add ?>"></script>
<script type="text/javascript" charset="utf-8" src="viewfeed.js?<?php echo $dt_add ?>"></script>
+ <script type="text/javascript" charset="utf-8" src="offline.js?<?php echo $dt_add ?>"></script>
+
+ <script type="text/javascript" src="gears_init.js"></script>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
@@ -110,7 +113,9 @@ window.onload = init;
</div>
<div id="header">
- <div class="topLinks">
+ <div class="topLinks" id="topLinks">
+
+ <span id="topLinksOnline">
<?php if (!SINGLE_USER_MODE) { ?>
<?php echo __('Hello,') ?> <b><?php echo $_SESSION["name"] ?></b> |
@@ -126,11 +131,24 @@ window.onload = init;
| <a href="logout.php"><?php echo __('Logout') ?></a>
<?php } ?>
+ <img id="restartOfflinePic" src="images/offline.png" style="display:none"
+ onclick="gotoOffline()"
+ title="<?php echo __('Restart in offline mode') ?>"/>
+
<img id="newVersionIcon" style="display:none;" onclick="javascript:explainError(2)"
src="images/new_version.png" title="<?php echo __('New version of Tiny Tiny RSS is available!') ?>"
alt="new_version_icon"/>
+ </span>
+
+ <span id="topLinksOffline" style="display : none">
+ <img id="restartOnlinePic" src="images/online.png"
+ onclick="gotoOnline()"
+ title="<?php echo __('Restart in online mode') ?>"/>
+ </span>
+
</div>
+
<img src="<?php echo $theme_image_path ?>images/ttrss_logo.png" alt="Tiny Tiny RSS"/>
</div>
@@ -172,8 +190,9 @@ window.onload = init;
<option value="qmcAddFilter">&nbsp;&nbsp;<?php echo __('Create filter...') ?></option>
<option value="qmcResetUI">&nbsp;&nbsp;<?php echo __('Reset UI layout') ?></option>
<option value="qmcResetCats">&nbsp;&nbsp;<?php echo __('Reset category order') ?></option>
-
+ <option id="qmcDownload" style="display : none" value="qmcDownload"><?php echo __('&nbsp;&nbsp;Download new articles...') ?></option>
<option value="qmcHKhelp"><?php echo __('&nbsp;&nbsp;Keyboard shortcuts') ?></option>
+
</select>
</div>
@@ -240,7 +259,7 @@ window.onload = init;
&nbsp;
- <input class="button" type="submit"
+ <input class="button" type="submit" name="update"
onclick="return viewCurrentFeed('ForceUpdate')"
value="<?php echo __('Update') ?>"/>
diff --git a/viewfeed.js b/viewfeed.js
index 787d49e3b..acce5310d 100644
--- a/viewfeed.js
+++ b/viewfeed.js
@@ -343,6 +343,10 @@ function article_callback2(transport, id, feed_id) {
showArticleInHeadlines(id);
+ if (db) {
+ db.execute("UPDATE articles SET unread = 0 WHERE id = ?", [id]);
+ }
+
var reply = transport.responseXML.firstChild.firstChild;
} else {
@@ -362,14 +366,16 @@ function article_callback2(transport, id, feed_id) {
setTimeout('updateFeedList(false, false)', 50);
_reload_feedlist_after_view = false;
} else {
- var counters = transport.responseXML.getElementsByTagName("counters")[0];
+ if (transport.responseXML) {
+ var counters = transport.responseXML.getElementsByTagName("counters")[0];
- if (counters) {
- debug("parsing piggybacked counters: " + counters);
- parse_counters(counters, false);
- } else {
- debug("counters container not found in reply, requesting...");
- request_counters();
+ if (counters) {
+ debug("parsing piggybacked counters: " + counters);
+ parse_counters(counters, false);
+ } else {
+ debug("counters container not found in reply, requesting...");
+ request_counters();
+ }
}
}
@@ -383,7 +389,9 @@ function view(id, feed_id, skip_history) {
try {
debug("loading article: " + id + "/" + feed_id);
-
+
+ if (offline_mode) return view_offline(id, feed_id);
+
var cached_article = cache_find(id);
debug("cache check result: " + (cached_article != false));
@@ -535,6 +543,10 @@ function toggleMark(id, client_only, no_effects) {
mark_img.alt = __("Unstar article");
query = query + "&mark=1";
+ if (db) {
+ db.execute("UPDATE articles SET marked = 1 WHERE id = ?", [id]);
+ }
+
} else {
//mark_img.src = "images/mark_unset.png";
mark_img.alt = __("Please wait...");
@@ -546,8 +558,15 @@ function toggleMark(id, client_only, no_effects) {
mark_img.src = mark_img.src.replace("mark_set", "mark_unset");
mark_img.alt = __("Star article");
}
+
+ if (db) {
+ db.execute("UPDATE articles SET marked = 0 WHERE id = ?", [id]);
+ }
+
}
+ update_local_feedlist_counters();
+
if (!client_only) {
debug(query);
@@ -792,6 +811,12 @@ function toggleUnread(id, cmode, effect) {
} else {
row.className = nc + "Unread";
}
+
+ if (db) {
+ db.execute("UPDATE articles SET unread = not unread "+
+ "WHERE id = ?", [id]);
+ }
+
} else if (cmode == 0) {
row.className = nc;
@@ -800,10 +825,24 @@ function toggleUnread(id, cmode, effect) {
afterFinish: toggleUnread_afh,
queue: { position:'end', scope: 'TMRQ-' + id, limit: 1 } } );
}
+
+ if (db) {
+ db.execute("UPDATE articles SET unread = 0 "+
+ "WHERE id = ?", [id]);
+ }
+
} else if (cmode == 1) {
row.className = nc + "Unread";
+
+ if (db) {
+ db.execute("UPDATE articles SET unread = 1 "+
+ "WHERE id = ?", [id]);
+ }
+
}
+ update_local_feedlist_counters();
+
// Disable unmarking as selected for the time being (16.05.08) -fox
if (is_selected) row.className = row.className + "Selected";
@@ -821,7 +860,6 @@ function toggleUnread(id, cmode, effect) {
}
-
} catch (e) {
exception_error("toggleUnread", e);
}
@@ -1466,65 +1504,150 @@ function cdmWatchdog() {
function cache_inject(id, article, param) {
- if (!cache_check_param(id, param)) {
- debug("cache_article: miss: " + id + " [p=" + param + "]");
-
- var cache_obj = new Array();
-
- cache_obj["id"] = id;
- cache_obj["data"] = article;
- cache_obj["param"] = param;
+ try {
+ if (!cache_check_param(id, param)) {
+ debug("cache_article: miss: " + id + " [p=" + param + "]");
+
+
+ if (db) {
- article_cache.push(cache_obj);
+ var date = new Date();
+ var ts = Math.round(date.getTime() / 1000);
- } else {
- debug("cache_article: hit: " + id + " [p=" + param + "]");
+ db.execute("INSERT INTO cache (id, article, param, added) VALUES (?, ?, ?, ?)",
+ [id, article, param, ts]);
+ } else {
+
+ var cache_obj = new Array();
+
+ cache_obj["id"] = id;
+ cache_obj["data"] = article;
+ cache_obj["param"] = param;
+
+ article_cache.push(cache_obj);
+ }
+
+ } else {
+ debug("cache_article: hit: " + id + " [p=" + param + "]");
+ }
+ } catch (e) {
+ exception_error("cache_inject", e);
}
}
function cache_find(id) {
- for (var i = 0; i < article_cache.length; i++) {
- if (article_cache[i]["id"] == id) {
- return article_cache[i]["data"];
+
+ if (db) {
+ var rs = db.execute("SELECT article FROM cache WHERE id = ?", [id]);
+ var a = false;
+
+ if (rs.isValidRow()) {
+ var a = rs.field(0);
+ }
+
+ rs.close();
+
+ return a;
+
+ } else {
+ for (var i = 0; i < article_cache.length; i++) {
+ if (article_cache[i]["id"] == id) {
+ return article_cache[i]["data"];
+ }
}
}
return false;
}
function cache_find_param(id, param) {
- for (var i = 0; i < article_cache.length; i++) {
- if (article_cache[i]["id"] == id && article_cache[i]["param"] == param) {
- return article_cache[i]["data"];
+
+ if (db) {
+ var rs = db.execute("SELECT article FROM cache WHERE id = ? AND param = ?",
+ [id, param]);
+ var a = false;
+
+ if (rs.isValidRow()) {
+ a = rs.field(0);
+ }
+
+ rs.close();
+
+ return a;
+
+ } else {
+ for (var i = 0; i < article_cache.length; i++) {
+ if (article_cache[i]["id"] == id && article_cache[i]["param"] == param) {
+ return article_cache[i]["data"];
+ }
}
}
return false;
}
function cache_check(id) {
- for (var i = 0; i < article_cache.length; i++) {
- if (article_cache[i]["id"] == id) {
- return true;
+
+ if (db) {
+ var rs = db.execute("SELECT COUNT(*) AS c FROM cache WHERE id = ?",
+ [id]);
+ var a = false;
+
+ if (rs.isValidRow()) {
+ a = rs.field(0) != "0";
+ }
+
+ rs.close();
+
+ return a;
+
+ } else {
+ for (var i = 0; i < article_cache.length; i++) {
+ if (article_cache[i]["id"] == id) {
+ return true;
+ }
}
}
return false;
}
function cache_check_param(id, param) {
- for (var i = 0; i < article_cache.length; i++) {
-// debug("cache_check_param " + article_cache[i]["id"] + ":" +
-// article_cache[i]["param"] + " vs " + id + ":" + param);
+ if (db) {
+ var rs = db.execute("SELECT COUNT(*) AS c FROM cache WHERE id = ? AND param = ?",
+ [id, param]);
+ var a = false;
- if (article_cache[i]["id"] == id && article_cache[i]["param"] == param) {
- return true;
+ if (rs.isValidRow()) {
+ a = rs.field(0) != "0";
+ }
+
+ rs.close();
+
+ return a;
+
+ } else {
+ for (var i = 0; i < article_cache.length; i++) {
+ if (article_cache[i]["id"] == id && article_cache[i]["param"] == param) {
+ return true;
+ }
}
}
return false;
}
function cache_expire() {
- while (article_cache.length > 25) {
- article_cache.shift();
+ if (db) {
+ var date = new Date();
+ var ts = Math.round(date.getTime() / 1000);
+
+ db.execute("DELETE FROM cache WHERE added < ? - 1800 AND id LIKE 'FEEDLIST'", [ts]);
+ db.execute("DELETE FROM cache WHERE added < ? - 600 AND (id LIKE 'F:%' OR id LIKE 'C:%')", [ts]);
+ db.execute("DELETE FROM cache WHERE added < ? - 86400", [ts]);
+
+
+ } else {
+ while (article_cache.length > 25) {
+ article_cache.shift();
+ }
}
}
@@ -1533,18 +1656,25 @@ function cache_empty() {
}
function cache_invalidate(id) {
- var i = 0
-
try {
- while (i < article_cache.length) {
- if (article_cache[i]["id"] == id) {
- debug("cache_invalidate: removed id " + id);
- article_cache.splice(i, 1);
- return true;
+ if (db) {
+ rs = db.execute("DELETE FROM cache WHERE id = ?", [id]);
+ return rs.rowsAffected != 0;
+ } else {
+
+ var i = 0
+
+ while (i < article_cache.length) {
+ if (article_cache[i]["id"] == id) {
+ debug("cache_invalidate: removed id " + id);
+ article_cache.splice(i, 1);
+ return true;
+ }
+ i++;
}
- i++;
}
+
debug("cache_invalidate: id not found: " + id);
return false;
} catch (e) {