feedlist.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631
  1. var _infscroll_disable = 0;
  2. var _infscroll_request_sent = 0;
  3. var _search_query = false;
  4. var _viewfeed_last = 0;
  5. var _viewfeed_timeout = false;
  6. var counters_last_request = 0;
  7. var _counters_prev = [];
  8. /*function viewCategory(cat) {
  9. viewfeed({feed: cat, is_cat: true});
  10. return false;
  11. }*/
  12. function resetCounterCache() {
  13. _counters_prev = [];
  14. }
  15. function loadMoreHeadlines() {
  16. try {
  17. console.log("loadMoreHeadlines");
  18. var offset = 0;
  19. var view_mode = document.forms["main_toolbar_form"].view_mode.value;
  20. var unread_in_buffer = $$("#headlines-frame > div[id*=RROW][class*=Unread]").length;
  21. var num_all = $$("#headlines-frame > div[id*=RROW]").length;
  22. var num_unread = getFeedUnread(getActiveFeedId(), activeFeedIsCat());
  23. // TODO implement marked & published
  24. if (view_mode == "marked") {
  25. console.warn("loadMoreHeadlines: marked is not implemented, falling back.");
  26. offset = num_all;
  27. } else if (view_mode == "published") {
  28. console.warn("loadMoreHeadlines: published is not implemented, falling back.");
  29. offset = num_all;
  30. } else if (view_mode == "unread") {
  31. offset = unread_in_buffer;
  32. } else if (_search_query) {
  33. offset = num_all;
  34. } else if (view_mode == "adaptive" && !(getActiveFeedId() == -1 && !activeFeedIsCat())) {
  35. // ^ starred feed shows both unread & read articles in adaptive mode
  36. offset = num_unread > 0 ? unread_in_buffer : num_all;
  37. } else {
  38. offset = num_all;
  39. }
  40. console.log("offset: " + offset);
  41. viewfeed({feed: getActiveFeedId(), is_cat: activeFeedIsCat(), offset: offset, infscroll_req: true});
  42. } catch (e) {
  43. exception_error("viewNextFeedPage", e);
  44. }
  45. }
  46. function cleanup_memory(root) {
  47. try {
  48. var dijits = dojo.query("[widgetid]", dijit.byId(root).domNode).map(dijit.byNode);
  49. dijits.each(function (d) {
  50. dojo.destroy(d.domNode);
  51. });
  52. $$("#" + root + " *").each(function (i) {
  53. i.parentNode ? i.parentNode.removeChild(i) : true;
  54. });
  55. } catch (e) {
  56. console.log("cleanup_memory: exception");
  57. console.log(e);
  58. }
  59. }
  60. function viewfeed(params) {
  61. try {
  62. var feed = params.feed;
  63. var is_cat = params.is_cat;
  64. var offset = params.offset;
  65. var background = params.background;
  66. var infscroll_req = params.infscroll_req;
  67. var can_wait = params.can_wait;
  68. var viewfeed_debug = params.viewfeed_debug;
  69. var method = params.method;
  70. if (is_cat == undefined)
  71. is_cat = false;
  72. else
  73. is_cat = !!is_cat;
  74. if (offset == undefined) offset = 0;
  75. if (background == undefined) background = false;
  76. if (infscroll_req == undefined) infscroll_req = false;
  77. last_requested_article = 0;
  78. if (feed != getActiveFeedId() || activeFeedIsCat() != is_cat) {
  79. if (!background && _search_query) _search_query = false;
  80. }
  81. if (!background) {
  82. _viewfeed_last = get_timestamp();
  83. if (getActiveFeedId() != feed || !infscroll_req) {
  84. setActiveArticleId(0);
  85. _infscroll_disable = 0;
  86. cleanup_memory("headlines-frame");
  87. _headlines_scroll_offset = 0;
  88. }
  89. if (infscroll_req) {
  90. var timestamp = get_timestamp();
  91. if (_infscroll_request_sent && _infscroll_request_sent + 30 > timestamp) {
  92. //console.log("infscroll request in progress, aborting");
  93. return;
  94. }
  95. _infscroll_request_sent = timestamp;
  96. }
  97. }
  98. Form.enable("main_toolbar_form");
  99. var toolbar_query = Form.serialize("main_toolbar_form");
  100. var query = "?op=feeds&method=view&feed=" + param_escape(feed) + "&" +
  101. toolbar_query;
  102. if (method) query += "&m=" + param_escape(method);
  103. if (offset > 0) {
  104. if (current_first_id) {
  105. query = query + "&fid=" + param_escape(current_first_id);
  106. }
  107. }
  108. if (!background) {
  109. if (_search_query) {
  110. force_nocache = true;
  111. query = query + "&" + _search_query;
  112. //_search_query = false;
  113. }
  114. if (offset != 0) {
  115. query = query + "&skip=" + offset;
  116. // to prevent duplicate feed titles when showing grouped vfeeds
  117. if (vgroup_last_feed) {
  118. query = query + "&vgrlf=" + param_escape(vgroup_last_feed);
  119. }
  120. } else {
  121. if (!is_cat && feed == getActiveFeedId() && !params.method) {
  122. query = query + "&m=ForceUpdate";
  123. }
  124. }
  125. Form.enable("main_toolbar_form");
  126. if (!setFeedExpandoIcon(feed, is_cat,
  127. (is_cat) ? 'images/indicator_tiny.gif' : 'images/indicator_white.gif'))
  128. notify_progress("Loading, please wait...", true);
  129. }
  130. query += "&cat=" + is_cat;
  131. console.log(query);
  132. if (can_wait && _viewfeed_timeout) {
  133. setFeedExpandoIcon(getActiveFeedId(), activeFeedIsCat(), 'images/blank_icon.gif');
  134. clearTimeout(_viewfeed_timeout);
  135. }
  136. setActiveFeedId(feed, is_cat);
  137. if (viewfeed_debug) {
  138. window.open("backend.php" + query + "&debug=1&csrf_token=" + getInitParam("csrf_token"));
  139. }
  140. timeout_ms = can_wait ? 250 : 0;
  141. _viewfeed_timeout = setTimeout(function() {
  142. new Ajax.Request("backend.php", {
  143. parameters: query,
  144. onComplete: function(transport) {
  145. setFeedExpandoIcon(feed, is_cat, 'images/blank_icon.gif');
  146. headlines_callback2(transport, offset, background, infscroll_req);
  147. PluginHost.run(PluginHost.HOOK_FEED_LOADED, [feed, is_cat]);
  148. } });
  149. }, timeout_ms); // Wait 250ms
  150. } catch (e) {
  151. exception_error("viewfeed", e);
  152. }
  153. }
  154. function feedlist_init() {
  155. try {
  156. console.log("in feedlist init");
  157. loading_set_progress(50);
  158. document.onkeydown = hotkey_handler;
  159. setTimeout(hotkey_prefix_timeout, 5*1000);
  160. if (!getActiveFeedId()) {
  161. viewfeed({feed: -3});
  162. } else {
  163. viewfeed({feed: getActiveFeedId(), is_cat: activeFeedIsCat()});
  164. }
  165. hideOrShowFeeds(getInitParam("hide_read_feeds") == 1);
  166. request_counters(true);
  167. timeout();
  168. } catch (e) {
  169. exception_error("feedlist/init", e);
  170. }
  171. }
  172. function request_counters(force) {
  173. try {
  174. var date = new Date();
  175. var timestamp = Math.round(date.getTime() / 1000);
  176. if (force || timestamp - counters_last_request > 5) {
  177. console.log("scheduling request of counters...");
  178. counters_last_request = timestamp;
  179. var query = "?op=rpc&method=getAllCounters&seq=" + next_seq();
  180. if (!force)
  181. query = query + "&last_article_id=" + getInitParam("last_article_id");
  182. console.log(query);
  183. new Ajax.Request("backend.php", {
  184. parameters: query,
  185. onComplete: function(transport) {
  186. try {
  187. handle_rpc_json(transport);
  188. } catch (e) {
  189. exception_error("request_counters", e);
  190. }
  191. } });
  192. } else {
  193. console.log("request_counters: rate limit reached: " + (timestamp - counters_last_request));
  194. }
  195. } catch (e) {
  196. exception_error("request_counters", e);
  197. }
  198. }
  199. // NOTE: this implementation is incomplete
  200. // for general objects but good enough for counters
  201. // http://adripofjavascript.com/blog/drips/object-equality-in-javascript.html
  202. function counter_is_equal(a, b) {
  203. // Create arrays of property names
  204. var aProps = Object.getOwnPropertyNames(a);
  205. var bProps = Object.getOwnPropertyNames(b);
  206. // If number of properties is different,
  207. // objects are not equivalent
  208. if (aProps.length != bProps.length) {
  209. return false;
  210. }
  211. for (var i = 0; i < aProps.length; i++) {
  212. var propName = aProps[i];
  213. // If values of same property are not equal,
  214. // objects are not equivalent
  215. if (a[propName] !== b[propName]) {
  216. return false;
  217. }
  218. }
  219. // If we made it this far, objects
  220. // are considered equivalent
  221. return true;
  222. }
  223. function parse_counters(elems) {
  224. try {
  225. for (var l = 0; l < elems.length; l++) {
  226. if (_counters_prev[l] && counter_is_equal(elems[l], _counters_prev[l])) {
  227. continue;
  228. }
  229. var id = elems[l].id;
  230. var kind = elems[l].kind;
  231. var ctr = parseInt(elems[l].counter);
  232. var error = elems[l].error;
  233. var has_img = elems[l].has_img;
  234. var updated = elems[l].updated;
  235. var auxctr = parseInt(elems[l].auxcounter);
  236. if (id == "global-unread") {
  237. global_unread = ctr;
  238. updateTitle();
  239. continue;
  240. }
  241. if (id == "subscribed-feeds") {
  242. /* feeds_found = ctr; */
  243. continue;
  244. }
  245. /*if (getFeedUnread(id, (kind == "cat")) != ctr ||
  246. (kind == "cat")) {
  247. }*/
  248. setFeedUnread(id, (kind == "cat"), ctr);
  249. setFeedValue(id, (kind == "cat"), 'auxcounter', auxctr);
  250. if (kind != "cat") {
  251. setFeedValue(id, false, 'error', error);
  252. setFeedValue(id, false, 'updated', updated);
  253. if (id > 0) {
  254. if (has_img) {
  255. setFeedIcon(id, false,
  256. getInitParam("icons_url") + "/" + id + ".ico");
  257. } else {
  258. setFeedIcon(id, false, 'images/blank_icon.gif');
  259. }
  260. }
  261. }
  262. }
  263. hideOrShowFeeds(getInitParam("hide_read_feeds") == 1);
  264. _counters_prev = elems;
  265. } catch (e) {
  266. exception_error("parse_counters", e);
  267. }
  268. }
  269. function getFeedUnread(feed, is_cat) {
  270. try {
  271. var tree = dijit.byId("feedTree");
  272. if (tree && tree.model)
  273. return tree.model.getFeedUnread(feed, is_cat);
  274. } catch (e) {
  275. //
  276. }
  277. return -1;
  278. }
  279. function getFeedCategory(feed) {
  280. try {
  281. var tree = dijit.byId("feedTree");
  282. if (tree && tree.model)
  283. return tree.getFeedCategory(feed);
  284. } catch (e) {
  285. //
  286. }
  287. return false;
  288. }
  289. function hideOrShowFeeds(hide) {
  290. var tree = dijit.byId("feedTree");
  291. if (tree)
  292. return tree.hideRead(hide, getInitParam("hide_read_shows_special"));
  293. }
  294. function getFeedName(feed, is_cat) {
  295. if (isNaN(feed)) return feed; // it's a tag
  296. var tree = dijit.byId("feedTree");
  297. if (tree && tree.model)
  298. return tree.model.getFeedValue(feed, is_cat, 'name');
  299. }
  300. function getFeedValue(feed, is_cat, key) {
  301. try {
  302. var tree = dijit.byId("feedTree");
  303. if (tree && tree.model)
  304. return tree.model.getFeedValue(feed, is_cat, key);
  305. } catch (e) {
  306. //
  307. }
  308. return '';
  309. }
  310. function setFeedUnread(feed, is_cat, unread) {
  311. try {
  312. var tree = dijit.byId("feedTree");
  313. if (tree && tree.model)
  314. return tree.model.setFeedUnread(feed, is_cat, unread);
  315. } catch (e) {
  316. exception_error("setFeedUnread", e);
  317. }
  318. }
  319. function setFeedValue(feed, is_cat, key, value) {
  320. try {
  321. var tree = dijit.byId("feedTree");
  322. if (tree && tree.model)
  323. return tree.model.setFeedValue(feed, is_cat, key, value);
  324. } catch (e) {
  325. //
  326. }
  327. }
  328. function selectFeed(feed, is_cat) {
  329. try {
  330. var tree = dijit.byId("feedTree");
  331. if (tree) return tree.selectFeed(feed, is_cat);
  332. } catch (e) {
  333. exception_error("selectFeed", e);
  334. }
  335. }
  336. function setFeedIcon(feed, is_cat, src) {
  337. try {
  338. var tree = dijit.byId("feedTree");
  339. if (tree) return tree.setFeedIcon(feed, is_cat, src);
  340. } catch (e) {
  341. exception_error("setFeedIcon", e);
  342. }
  343. }
  344. function setFeedExpandoIcon(feed, is_cat, src) {
  345. try {
  346. var tree = dijit.byId("feedTree");
  347. if (tree) return tree.setFeedExpandoIcon(feed, is_cat, src);
  348. } catch (e) {
  349. exception_error("setFeedIcon", e);
  350. }
  351. return false;
  352. }
  353. function getNextUnreadFeed(feed, is_cat) {
  354. try {
  355. var tree = dijit.byId("feedTree");
  356. var nuf = tree.model.getNextUnreadFeed(feed, is_cat);
  357. if (nuf)
  358. return tree.model.store.getValue(nuf, 'bare_id');
  359. } catch (e) {
  360. exception_error("getNextUnreadFeed", e);
  361. }
  362. }
  363. function catchupCurrentFeed(mode) {
  364. catchupFeed(getActiveFeedId(), activeFeedIsCat(), mode);
  365. }
  366. function catchupFeedInGroup(id) {
  367. try {
  368. var title = getFeedName(id);
  369. var str = __("Mark all articles in %s as read?").replace("%s", title);
  370. if (getInitParam("confirm_feed_catchup") != 1 || confirm(str)) {
  371. var rows = $$("#headlines-frame > div[id*=RROW][data-orig-feed-id='"+id+"']");
  372. if (rows.length > 0) {
  373. rows.each(function (row) {
  374. row.removeClassName("Unread");
  375. if (row.getAttribute("data-article-id") != getActiveArticleId()) {
  376. new Effect.Fade(row, {duration: 0.5});
  377. }
  378. });
  379. var feedTitles = $$("#headlines-frame > div[class='cdmFeedTitle']");
  380. for (var i = 0; i < feedTitles.length; i++) {
  381. if (feedTitles[i].getAttribute("data-feed-id") == id) {
  382. if (i < feedTitles.length - 1) {
  383. new Effect.Fade(feedTitles[i], {duration: 0.5});
  384. }
  385. break;
  386. }
  387. }
  388. updateFloatingTitle(true);
  389. }
  390. var catchup_query = "?op=rpc&method=catchupFeed&feed_id=" +
  391. id + "&is_cat=false";
  392. console.log(catchup_query);
  393. notify_progress("Loading, please wait...", true);
  394. new Ajax.Request("backend.php", {
  395. parameters: catchup_query,
  396. onComplete: function (transport) {
  397. handle_rpc_json(transport);
  398. }
  399. } );
  400. //return viewCurrentFeed('MarkAllReadGR:' + id);
  401. }
  402. } catch (e) {
  403. exception_error("catchupFeedInGroup", e);
  404. }
  405. }
  406. function catchupFeed(feed, is_cat, mode) {
  407. try {
  408. if (is_cat == undefined) is_cat = false;
  409. var str = false;
  410. switch (mode) {
  411. case "1day":
  412. str = __("Mark all articles in %s older than 1 day as read?");
  413. break;
  414. case "1week":
  415. str = __("Mark all articles in %s older than 1 week as read?");
  416. break;
  417. case "2week":
  418. str = __("Mark all articles in %s older than 2 weeks as read?");
  419. break;
  420. default:
  421. str = __("Mark all articles in %s as read?");
  422. }
  423. var fn = getFeedName(feed, is_cat);
  424. str = str.replace("%s", fn);
  425. if (getInitParam("confirm_feed_catchup") == 1 && !confirm(str)) {
  426. return;
  427. }
  428. var catchup_query = "?op=rpc&method=catchupFeed&feed_id=" +
  429. feed + "&is_cat=" + is_cat + "&mode=" + mode;
  430. console.log(catchup_query);
  431. notify_progress("Loading, please wait...", true);
  432. new Ajax.Request("backend.php", {
  433. parameters: catchup_query,
  434. onComplete: function(transport) {
  435. handle_rpc_json(transport);
  436. var show_next_feed = getInitParam("on_catchup_show_next_feed") == "1";
  437. if (show_next_feed) {
  438. var nuf = getNextUnreadFeed(feed, is_cat);
  439. if (nuf) {
  440. viewfeed({feed: nuf, is_cat: is_cat});
  441. }
  442. } else {
  443. if (feed == getActiveFeedId() && is_cat == activeFeedIsCat()) {
  444. viewCurrentFeed();
  445. }
  446. }
  447. notify("");
  448. } });
  449. } catch (e) {
  450. exception_error("catchupFeed", e);
  451. }
  452. }
  453. function decrementFeedCounter(feed, is_cat) {
  454. try {
  455. var ctr = getFeedUnread(feed, is_cat);
  456. if (ctr > 0) {
  457. setFeedUnread(feed, is_cat, ctr - 1);
  458. global_unread = global_unread - 1;
  459. updateTitle();
  460. if (!is_cat) {
  461. var cat = parseInt(getFeedCategory(feed));
  462. if (!isNaN(cat)) {
  463. ctr = getFeedUnread(cat, true);
  464. if (ctr > 0) {
  465. setFeedUnread(cat, true, ctr - 1);
  466. }
  467. }
  468. }
  469. }
  470. } catch (e) {
  471. exception_error("decrement_feed_counter", e);
  472. }
  473. }